Skip to content

Commit 6e53425

Browse files
committed
refactor: replace unreachable! with compile-time type safety in execute_leaf
Pass &ExecutionItemDisplay and &LeafExecutionKind separately instead of &ExecutionItem. The caller already matches on Leaf(leaf_kind), so decomposing at the call site eliminates the unreachable! branch — the compiler now enforces that execute_leaf can only receive leaf items.
1 parent 4427f82 commit 6e53425

File tree

3 files changed

+65
-28
lines changed

3 files changed

+65
-28
lines changed

crates/vite_task/src/session/execute/mod.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use futures_util::FutureExt;
77
use tokio::io::AsyncWriteExt as _;
88
use vite_path::AbsolutePath;
99
use vite_task_plan::{
10-
ExecutionGraph, ExecutionItem, ExecutionItemKind, LeafExecutionKind, SpawnCommand,
10+
ExecutionGraph, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind, SpawnCommand,
1111
SpawnExecution,
1212
};
1313

@@ -91,8 +91,14 @@ impl ExecutionContext<'_> {
9191

9292
for item in &task_execution.items {
9393
match &item.kind {
94-
ExecutionItemKind::Leaf(_) => {
95-
self.execute_leaf(item, all_ancestors_single_node).boxed_local().await;
94+
ExecutionItemKind::Leaf(leaf_kind) => {
95+
self.execute_leaf(
96+
&item.execution_item_display,
97+
leaf_kind,
98+
all_ancestors_single_node,
99+
)
100+
.boxed_local()
101+
.await;
96102
}
97103
ExecutionItemKind::Expanded(nested_graph) => {
98104
self.execute_expanded_graph(
@@ -112,14 +118,16 @@ impl ExecutionContext<'_> {
112118
/// Creates a [`LeafExecutionReporter`] from the graph reporter and delegates
113119
/// to the appropriate execution method.
114120
#[expect(clippy::future_not_send, reason = "uses !Send types internally")]
115-
async fn execute_leaf(&mut self, item: &ExecutionItem, all_ancestors_single_node: bool) {
116-
let mut leaf_reporter = self.reporter.new_leaf_execution(item, all_ancestors_single_node);
117-
118-
let ExecutionItemKind::Leaf(leaf_execution_kind) = &item.kind else {
119-
unreachable!("execute_leaf called with non-leaf ExecutionItem");
120-
};
121+
async fn execute_leaf(
122+
&mut self,
123+
display: &ExecutionItemDisplay,
124+
leaf_kind: &LeafExecutionKind,
125+
all_ancestors_single_node: bool,
126+
) {
127+
let mut leaf_reporter =
128+
self.reporter.new_leaf_execution(display, leaf_kind, all_ancestors_single_node);
121129

122-
match leaf_execution_kind {
130+
match leaf_kind {
123131
LeafExecutionKind::InProcess(in_process_execution) => {
124132
// In-process (built-in) commands: caching is disabled, execute synchronously
125133
let mut stdio_config = leaf_reporter

crates/vite_task/src/session/reporter/labeled.rs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use owo_colors::Style;
1212
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
1313
use vite_path::AbsolutePath;
1414
use vite_str::Str;
15-
use vite_task_plan::{ExecutionItem, ExecutionItemDisplay, ExecutionItemKind, LeafExecutionKind};
15+
use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind};
1616

1717
use super::{
1818
CACHE_MISS_STYLE, COMMAND_STYLE, ColorizeExt, ExitStatus, GraphExecutionReporter,
@@ -119,14 +119,13 @@ pub struct LabeledGraphReporter {
119119
impl GraphExecutionReporter for LabeledGraphReporter {
120120
fn new_leaf_execution(
121121
&mut self,
122-
item: &ExecutionItem,
122+
display: &ExecutionItemDisplay,
123+
leaf_kind: &LeafExecutionKind,
123124
all_ancestors_single_node: bool,
124125
) -> Box<dyn LeafExecutionReporter> {
125-
let display = item.execution_item_display.clone();
126-
let stdio_suggestion = match &item.kind {
127-
ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(_)) if all_ancestors_single_node => {
128-
StdioSuggestion::Inherited
129-
}
126+
let display = display.clone();
127+
let stdio_suggestion = match leaf_kind {
128+
LeafExecutionKind::Spawn(_) if all_ancestors_single_node => StdioSuggestion::Inherited,
130129
_ => StdioSuggestion::Piped,
131130
};
132131

@@ -552,6 +551,8 @@ fn format_summary(
552551

553552
#[cfg(test)]
554553
mod tests {
554+
use vite_task_plan::ExecutionItemKind;
555+
555556
use super::*;
556557
use crate::session::{
557558
event::CacheDisabledReason,
@@ -561,25 +562,36 @@ mod tests {
561562
},
562563
};
563564

565+
/// Extract the `LeafExecutionKind` from a test fixture item.
566+
/// Panics if the item is not a leaf (test fixtures always produce leaves).
567+
fn leaf_kind(item: &vite_task_plan::ExecutionItem) -> &LeafExecutionKind {
568+
match &item.kind {
569+
ExecutionItemKind::Leaf(kind) => kind,
570+
ExecutionItemKind::Expanded(_) => panic!("test fixture item must be a Leaf"),
571+
}
572+
}
573+
564574
fn build_labeled_leaf(
565-
item: &ExecutionItem,
575+
display: &ExecutionItemDisplay,
576+
leaf_kind: &LeafExecutionKind,
566577
all_ancestors_single_node: bool,
567578
) -> Box<dyn LeafExecutionReporter> {
568579
let builder =
569580
Box::new(LabeledReporterBuilder::new(test_path(), Box::new(tokio::io::sink())));
570581
let mut reporter = builder.build();
571-
reporter.new_leaf_execution(item, all_ancestors_single_node)
582+
reporter.new_leaf_execution(display, leaf_kind, all_ancestors_single_node)
572583
}
573584

574585
#[expect(
575586
clippy::future_not_send,
576587
reason = "LeafExecutionReporter futures are !Send in single-threaded reporter tests"
577588
)]
578589
async fn suggestion_for(
579-
item: &ExecutionItem,
590+
display: &ExecutionItemDisplay,
591+
leaf_kind: &LeafExecutionKind,
580592
all_ancestors_single_node: bool,
581593
) -> StdioSuggestion {
582-
let mut leaf = build_labeled_leaf(item, all_ancestors_single_node);
594+
let mut leaf = build_labeled_leaf(display, leaf_kind, all_ancestors_single_node);
583595
let stdio_config =
584596
leaf.start(CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata)).await;
585597
stdio_config.suggestion
@@ -588,24 +600,40 @@ mod tests {
588600
#[tokio::test]
589601
async fn spawn_with_all_single_node_ancestors_suggests_inherited() {
590602
let task = spawn_task("build");
591-
assert_eq!(suggestion_for(&task.items[0], true).await, StdioSuggestion::Inherited);
603+
let item = &task.items[0];
604+
assert_eq!(
605+
suggestion_for(&item.execution_item_display, leaf_kind(item), true).await,
606+
StdioSuggestion::Inherited
607+
);
592608
}
593609

594610
#[tokio::test]
595611
async fn spawn_without_all_single_node_ancestors_suggests_piped() {
596612
let task = spawn_task("build");
597-
assert_eq!(suggestion_for(&task.items[0], false).await, StdioSuggestion::Piped);
613+
let item = &task.items[0];
614+
assert_eq!(
615+
suggestion_for(&item.execution_item_display, leaf_kind(item), false).await,
616+
StdioSuggestion::Piped
617+
);
598618
}
599619

600620
#[tokio::test]
601621
async fn in_process_leaf_suggests_piped_even_with_single_node_ancestors() {
602622
let task = in_process_task("echo");
603-
assert_eq!(suggestion_for(&task.items[0], true).await, StdioSuggestion::Piped);
623+
let item = &task.items[0];
624+
assert_eq!(
625+
suggestion_for(&item.execution_item_display, leaf_kind(item), true).await,
626+
StdioSuggestion::Piped
627+
);
604628
}
605629

606630
#[tokio::test]
607631
async fn in_process_leaf_suggests_piped_without_single_node_ancestors() {
608632
let task = in_process_task("echo");
609-
assert_eq!(suggestion_for(&task.items[0], false).await, StdioSuggestion::Piped);
633+
let item = &task.items[0];
634+
assert_eq!(
635+
suggestion_for(&item.execution_item_display, leaf_kind(item), false).await,
636+
StdioSuggestion::Piped
637+
);
610638
}
611639
}

crates/vite_task/src/session/reporter/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub use plain::PlainReporter;
3333
use tokio::io::AsyncWrite;
3434
use vite_path::AbsolutePath;
3535
use vite_str::Str;
36-
use vite_task_plan::{ExecutionItem, ExecutionItemDisplay};
36+
use vite_task_plan::{ExecutionItemDisplay, LeafExecutionKind};
3737

3838
use super::{
3939
cache::format_cache_status_inline,
@@ -114,15 +114,16 @@ pub trait GraphExecutionReporterBuilder {
114114
/// and finalizes the session with `finish()`.
115115
#[async_trait::async_trait(?Send)]
116116
pub trait GraphExecutionReporter {
117-
/// Create a new leaf execution reporter for the given execution item.
117+
/// Create a new leaf execution reporter for the given leaf.
118118
///
119119
/// `all_ancestors_single_node` is `true` when every execution graph in
120120
/// the ancestry chain (root + all nested `Expanded` parents) contains
121121
/// exactly one node. The reporter may use this to decide stdio mode
122122
/// (e.g. suggesting inherited stdio for a single spawned process).
123123
fn new_leaf_execution(
124124
&mut self,
125-
item: &ExecutionItem,
125+
display: &ExecutionItemDisplay,
126+
leaf_kind: &LeafExecutionKind,
126127
all_ancestors_single_node: bool,
127128
) -> Box<dyn LeafExecutionReporter>;
128129

0 commit comments

Comments
 (0)