Skip to content

Commit f045ced

Browse files
hyperpolymathclaude
andcommitted
Revert 79 .expect("TODO: handle error") sites across 3 files (batch 3/N)
Continues the per-site reversal of the .unwrap() → .expect("TODO: handle error") anti-pattern flagged by CRG-AUDIT-2026-04-18. Per-site policy: * Test code → revert to .unwrap() (test convention; preceding asserts or short-circuits already prove the Some/Ok case). This batch: - quotes.rs (27 sites in tests::test_*) - redirection.rs (28 sites in tests::test_redirect_*, test_modification_*) - external.rs (21 sites in tests::test_execute_*, test_pipeline_*) * Production code, external.rs (3 sites): - L164 stdin_cfg: kept `expect` with documented invariant — when idx > 0, the previous iteration spawned a non-last child with Stdio::piped() stdout and stored child.stdout into prev_stdout. The expect now records the unreachable case rather than a TODO. - L168-185 stdout_cfg + stderr_cfg: restructured to pivot on `match &redirect_setup` instead of double-checking `if !redirects.is_empty()` and then `.expect()`-ing the Option. `redirect_setup` is Some iff `!redirects.is_empty()` (set above). Both panic sites eliminated; one duplicated emptiness check removed. Verified locally: cargo build -> EXIT 0 cargo test -> 703 passed / 0 failed / 14 ignored (unchanged across batches 1+2+3; restructure preserves behaviour exactly) Cumulative progress: 136 / ~530 sites cleared (~26%) across 13 files. Remaining big-3 in impl/rust-cli/src/: parser.rs (116), commands.rs (66), arith.rs (55) — each warrants its own dedicated session. Mid-tier left: state.rs (35). Plus impl/rust-ffi/ ↔ ffi/rust/ duplicate tree (~50 more, separate finding). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 962da88 commit f045ced

3 files changed

Lines changed: 95 additions & 89 deletions

File tree

impl/rust-cli/src/external.rs

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -160,28 +160,34 @@ pub fn execute_pipeline(
160160
// First stage: inherit from shell
161161
Stdio::inherit()
162162
} else {
163-
// Middle/last stages: read from previous stdout
164-
Stdio::from(prev_stdout.take().expect("TODO: handle error"))
163+
// Middle/last stages: read from previous stdout.
164+
// Invariant: when idx > 0, the previous iteration spawned a
165+
// non-last child with Stdio::piped() stdout (line below) and
166+
// stored child.stdout into prev_stdout after spawning. So the
167+
// expect documents an unreachable branch rather than a TODO.
168+
Stdio::from(prev_stdout.take().expect(
169+
"prev_stdout invariant: previous non-last stage stored its piped stdout",
170+
))
165171
};
166172

167-
// Configure stdout
173+
// Configure stdout. Pivot on redirect_setup directly: it is Some
174+
// iff redirects is non-empty (constructed above), so matching the
175+
// Option both removes a duplicated emptiness check and eliminates
176+
// a previous panic site.
168177
let stdout_cfg = if is_last {
169-
// Last stage: redirect to file or inherit
170-
if !redirects.is_empty() {
171-
stdio_config_from_redirects(redirects, redirect_setup.as_ref().expect("TODO: handle error"), state)?.1
172-
} else {
173-
Stdio::inherit()
178+
match &redirect_setup {
179+
Some(setup) => stdio_config_from_redirects(redirects, setup, state)?.1,
180+
None => Stdio::inherit(),
174181
}
175182
} else {
176183
// First/middle stages: pipe to next
177184
Stdio::piped()
178185
};
179186

180-
// Configure stderr (inherit unless redirected in last stage)
181-
let stderr_cfg = if is_last && !redirects.is_empty() {
182-
stdio_config_from_redirects(redirects, redirect_setup.as_ref().expect("TODO: handle error"), state)?.2
183-
} else {
184-
Stdio::inherit()
187+
// Configure stderr (inherit unless redirected in last stage).
188+
let stderr_cfg = match (is_last, &redirect_setup) {
189+
(true, Some(setup)) => stdio_config_from_redirects(redirects, setup, state)?.2,
190+
_ => Stdio::inherit(),
185191
};
186192

187193
// Spawn child
@@ -694,67 +700,67 @@ mod tests {
694700
#[test]
695701
fn test_external_command_success() {
696702
// Test successful command execution
697-
let exit_code = execute_external("true", &[]).expect("TODO: handle error");
703+
let exit_code = execute_external("true", &[]).unwrap();
698704
assert_eq!(exit_code, 0, "true command should return 0");
699705
}
700706

701707
#[test]
702708
fn test_external_command_failure() {
703709
// Test failed command execution
704-
let exit_code = execute_external("false", &[]).expect("TODO: handle error");
710+
let exit_code = execute_external("false", &[]).unwrap();
705711
assert_eq!(exit_code, 1, "false command should return 1");
706712
}
707713

708714
#[test]
709715
fn test_external_command_with_args() {
710716
// Test command with arguments
711-
let exit_code = execute_external("echo", &["hello".to_string()]).expect("TODO: handle error");
717+
let exit_code = execute_external("echo", &["hello".to_string()]).unwrap();
712718
assert_eq!(exit_code, 0, "echo should return 0");
713719
}
714720

715721
#[test]
716722
fn test_pipeline_simple() {
717723
// Test simple 2-stage pipeline: true | true
718724
use tempfile::TempDir;
719-
let temp = TempDir::new().expect("TODO: handle error");
720-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
725+
let temp = TempDir::new().unwrap();
726+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
721727

722728
let stages = vec![
723729
("true".to_string(), vec![]),
724730
("true".to_string(), vec![]),
725731
];
726-
let exit_code = execute_pipeline(&stages, &[], &mut state).expect("TODO: handle error");
732+
let exit_code = execute_pipeline(&stages, &[], &mut state).unwrap();
727733
assert_eq!(exit_code, 0, "true | true should return 0");
728734
}
729735

730736
#[test]
731737
fn test_pipeline_exit_code() {
732738
// Test that pipeline returns exit code from last stage
733739
use tempfile::TempDir;
734-
let temp = TempDir::new().expect("TODO: handle error");
735-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
740+
let temp = TempDir::new().unwrap();
741+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
736742

737743
let stages = vec![
738744
("true".to_string(), vec![]),
739745
("false".to_string(), vec![]),
740746
];
741-
let exit_code = execute_pipeline(&stages, &[], &mut state).expect("TODO: handle error");
747+
let exit_code = execute_pipeline(&stages, &[], &mut state).unwrap();
742748
assert_eq!(exit_code, 1, "true | false should return 1 (from false)");
743749
}
744750

745751
#[test]
746752
fn test_pipeline_three_stages() {
747753
// Test 3-stage pipeline
748754
use tempfile::TempDir;
749-
let temp = TempDir::new().expect("TODO: handle error");
750-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
755+
let temp = TempDir::new().unwrap();
756+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
751757

752758
let stages = vec![
753759
("echo".to_string(), vec!["hello".to_string()]),
754760
("cat".to_string(), vec![]),
755761
("cat".to_string(), vec![]),
756762
];
757-
let exit_code = execute_pipeline(&stages, &[], &mut state).expect("TODO: handle error");
763+
let exit_code = execute_pipeline(&stages, &[], &mut state).unwrap();
758764
assert_eq!(exit_code, 0, "echo hello | cat | cat should return 0");
759765
}
760766

@@ -763,8 +769,8 @@ mod tests {
763769
// Test pipeline with output redirection
764770
use tempfile::TempDir;
765771
use std::fs;
766-
let temp = TempDir::new().expect("TODO: handle error");
767-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
772+
let temp = TempDir::new().unwrap();
773+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
768774

769775
let stages = vec![
770776
("echo".to_string(), vec!["test".to_string()]),
@@ -773,23 +779,23 @@ mod tests {
773779
let redirects = vec![crate::redirection::Redirection::Output {
774780
file: "output.txt".to_string(),
775781
}];
776-
let exit_code = execute_pipeline(&stages, &redirects, &mut state).expect("TODO: handle error");
782+
let exit_code = execute_pipeline(&stages, &redirects, &mut state).unwrap();
777783
assert_eq!(exit_code, 0, "Pipeline with redirect should return 0");
778784

779785
// Verify file was created
780786
let output_file = temp.path().join("output.txt");
781787
assert!(output_file.exists(), "Output file should be created");
782788

783-
let content = fs::read_to_string(output_file).expect("TODO: handle error");
789+
let content = fs::read_to_string(output_file).unwrap();
784790
assert_eq!(content.trim(), "test", "Output should be 'test'");
785791
}
786792

787793
#[test]
788794
fn test_pipeline_error_empty_stages() {
789795
// Test that empty stages array returns error
790796
use tempfile::TempDir;
791-
let temp = TempDir::new().expect("TODO: handle error");
792-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
797+
let temp = TempDir::new().unwrap();
798+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
793799

794800
let stages: Vec<(String, Vec<String>)> = vec![];
795801
let result = execute_pipeline(&stages, &[], &mut state);
@@ -800,11 +806,11 @@ mod tests {
800806
fn test_pipeline_single_stage_delegates() {
801807
// Test that single-stage pipeline delegates to execute_external_with_redirects
802808
use tempfile::TempDir;
803-
let temp = TempDir::new().expect("TODO: handle error");
804-
let mut state = crate::state::ShellState::new(temp.path().to_str().expect("TODO: handle error")).expect("TODO: handle error");
809+
let temp = TempDir::new().unwrap();
810+
let mut state = crate::state::ShellState::new(temp.path().to_str().unwrap()).unwrap();
805811

806812
let stages = vec![("true".to_string(), vec![])];
807-
let exit_code = execute_pipeline(&stages, &[], &mut state).expect("TODO: handle error");
813+
let exit_code = execute_pipeline(&stages, &[], &mut state).unwrap();
808814
assert_eq!(exit_code, 0, "Single-stage pipeline should work");
809815
}
810816
}

impl/rust-cli/src/quotes.rs

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -305,31 +305,31 @@ mod tests {
305305

306306
#[test]
307307
fn test_single_quotes_literal() {
308-
let segments = parse_quotes("'$HOME'").expect("TODO: handle error");
308+
let segments = parse_quotes("'$HOME'").unwrap();
309309
assert_eq!(segments.len(), 1);
310310
assert_eq!(segments[0].content, "$HOME");
311311
assert_eq!(segments[0].state, QuoteState::SingleQuoted);
312312
}
313313

314314
#[test]
315315
fn test_double_quotes() {
316-
let segments = parse_quotes("\"$HOME\"").expect("TODO: handle error");
316+
let segments = parse_quotes("\"$HOME\"").unwrap();
317317
assert_eq!(segments.len(), 1);
318318
assert_eq!(segments[0].content, "$HOME");
319319
assert_eq!(segments[0].state, QuoteState::DoubleQuoted);
320320
}
321321

322322
#[test]
323323
fn test_unquoted() {
324-
let segments = parse_quotes("hello").expect("TODO: handle error");
324+
let segments = parse_quotes("hello").unwrap();
325325
assert_eq!(segments.len(), 1);
326326
assert_eq!(segments[0].content, "hello");
327327
assert_eq!(segments[0].state, QuoteState::Unquoted);
328328
}
329329

330330
#[test]
331331
fn test_adjacent_quotes() {
332-
let segments = parse_quotes("'hello'\"world\"").expect("TODO: handle error");
332+
let segments = parse_quotes("'hello'\"world\"").unwrap();
333333
assert_eq!(segments.len(), 2);
334334
assert_eq!(segments[0].content, "hello");
335335
assert_eq!(segments[0].state, QuoteState::SingleQuoted);
@@ -339,7 +339,7 @@ mod tests {
339339

340340
#[test]
341341
fn test_mixed_quotes() {
342-
let segments = parse_quotes("pre'quoted'post").expect("TODO: handle error");
342+
let segments = parse_quotes("pre'quoted'post").unwrap();
343343
assert_eq!(segments.len(), 3);
344344
assert_eq!(segments[0].content, "pre");
345345
assert_eq!(segments[0].state, QuoteState::Unquoted);
@@ -351,40 +351,40 @@ mod tests {
351351

352352
#[test]
353353
fn test_backslash_escape_unquoted() {
354-
let segments = parse_quotes("hello\\ world").expect("TODO: handle error");
354+
let segments = parse_quotes("hello\\ world").unwrap();
355355
assert_eq!(segments.len(), 1);
356356
assert_eq!(segments[0].content, "hello world");
357357
assert_eq!(segments[0].state, QuoteState::Unquoted);
358358
}
359359

360360
#[test]
361361
fn test_backslash_in_single_quotes() {
362-
let segments = parse_quotes("'back\\slash'").expect("TODO: handle error");
362+
let segments = parse_quotes("'back\\slash'").unwrap();
363363
assert_eq!(segments.len(), 1);
364364
assert_eq!(segments[0].content, "back\\slash");
365365
assert_eq!(segments[0].state, QuoteState::SingleQuoted);
366366
}
367367

368368
#[test]
369369
fn test_backslash_in_double_quotes() {
370-
let segments = parse_quotes("\"\\$HOME\"").expect("TODO: handle error");
370+
let segments = parse_quotes("\"\\$HOME\"").unwrap();
371371
assert_eq!(segments.len(), 1);
372372
assert_eq!(segments[0].content, "$HOME");
373373
assert_eq!(segments[0].state, QuoteState::DoubleQuoted);
374374

375375
// Backslash before non-special character stays
376-
let segments = parse_quotes("\"\\a\"").expect("TODO: handle error");
376+
let segments = parse_quotes("\"\\a\"").unwrap();
377377
assert_eq!(segments[0].content, "\\a");
378378
}
379379

380380
#[test]
381381
fn test_empty_quotes() {
382-
let segments = parse_quotes("''").expect("TODO: handle error");
382+
let segments = parse_quotes("''").unwrap();
383383
assert_eq!(segments.len(), 1);
384384
assert_eq!(segments[0].content, "");
385385
assert_eq!(segments[0].state, QuoteState::SingleQuoted);
386386

387-
let segments = parse_quotes("\"\"").expect("TODO: handle error");
387+
let segments = parse_quotes("\"\"").unwrap();
388388
assert_eq!(segments.len(), 1);
389389
assert_eq!(segments[0].content, "");
390390
assert_eq!(segments[0].state, QuoteState::DoubleQuoted);
@@ -407,74 +407,74 @@ mod tests {
407407
#[test]
408408
fn test_glob_no_expansion_in_quotes() {
409409
// Unquoted - should expand
410-
let segments = parse_quotes("*.txt").expect("TODO: handle error");
410+
let segments = parse_quotes("*.txt").unwrap();
411411
assert!(should_expand_glob(&segments));
412412

413413
// Single quoted - no expansion
414-
let segments = parse_quotes("'*.txt'").expect("TODO: handle error");
414+
let segments = parse_quotes("'*.txt'").unwrap();
415415
assert!(!should_expand_glob(&segments));
416416

417417
// Double quoted - no expansion
418-
let segments = parse_quotes("\"*.txt\"").expect("TODO: handle error");
418+
let segments = parse_quotes("\"*.txt\"").unwrap();
419419
assert!(!should_expand_glob(&segments));
420420
}
421421

422422
#[test]
423423
fn test_reconstruct_string() {
424-
let segments = parse_quotes("'hello'\" world\"").expect("TODO: handle error");
424+
let segments = parse_quotes("'hello'\" world\"").unwrap();
425425
let result = reconstruct_string(&segments);
426426
assert_eq!(result, "hello world");
427427

428-
let segments = parse_quotes("one'two'three").expect("TODO: handle error");
428+
let segments = parse_quotes("one'two'three").unwrap();
429429
let result = reconstruct_string(&segments);
430430
assert_eq!(result, "onetwothree");
431431
}
432432

433433
#[test]
434434
fn test_allows_variable_expansion() {
435-
let segments = parse_quotes("'$VAR'").expect("TODO: handle error");
435+
let segments = parse_quotes("'$VAR'").unwrap();
436436
assert!(!segments[0].allows_variable_expansion());
437437

438-
let segments = parse_quotes("\"$VAR\"").expect("TODO: handle error");
438+
let segments = parse_quotes("\"$VAR\"").unwrap();
439439
assert!(segments[0].allows_variable_expansion());
440440

441-
let segments = parse_quotes("$VAR").expect("TODO: handle error");
441+
let segments = parse_quotes("$VAR").unwrap();
442442
assert!(segments[0].allows_variable_expansion());
443443
}
444444

445445
#[test]
446446
fn test_allows_glob_expansion() {
447-
let segments = parse_quotes("'*.txt'").expect("TODO: handle error");
447+
let segments = parse_quotes("'*.txt'").unwrap();
448448
assert!(!segments[0].allows_glob_expansion());
449449

450-
let segments = parse_quotes("\"*.txt\"").expect("TODO: handle error");
450+
let segments = parse_quotes("\"*.txt\"").unwrap();
451451
assert!(!segments[0].allows_glob_expansion());
452452

453-
let segments = parse_quotes("*.txt").expect("TODO: handle error");
453+
let segments = parse_quotes("*.txt").unwrap();
454454
assert!(segments[0].allows_glob_expansion());
455455
}
456456

457457
#[test]
458458
fn test_line_continuation() {
459459
// Backslash-newline in unquoted context
460-
let segments = parse_quotes("hello\\\nworld").expect("TODO: handle error");
460+
let segments = parse_quotes("hello\\\nworld").unwrap();
461461
assert_eq!(segments[0].content, "helloworld");
462462

463463
// Backslash-newline in double quotes
464-
let segments = parse_quotes("\"hello\\\nworld\"").expect("TODO: handle error");
464+
let segments = parse_quotes("\"hello\\\nworld\"").unwrap();
465465
assert_eq!(segments[0].content, "helloworld");
466466

467467
// Backslash-newline in single quotes (literal)
468-
let segments = parse_quotes("'hello\\\nworld'").expect("TODO: handle error");
468+
let segments = parse_quotes("'hello\\\nworld'").unwrap();
469469
assert_eq!(segments[0].content, "hello\\\nworld");
470470
}
471471

472472
#[test]
473473
fn test_whitespace_preservation() {
474-
let segments = parse_quotes("'hello world'").expect("TODO: handle error");
474+
let segments = parse_quotes("'hello world'").unwrap();
475475
assert_eq!(segments[0].content, "hello world");
476476

477-
let segments = parse_quotes("\"hello world\"").expect("TODO: handle error");
477+
let segments = parse_quotes("\"hello world\"").unwrap();
478478
assert_eq!(segments[0].content, "hello world");
479479
}
480480
}

0 commit comments

Comments
 (0)