From 4e56cd5fc43226c9812f6a8b08bb8e033bd5d534 Mon Sep 17 00:00:00 2001 From: zc he Date: Thu, 7 Aug 2025 03:17:58 +0800 Subject: [PATCH] fix(parser): external argument with subexpressions (#16346) Fixes: #16040 # Description TBH, I not a fan of this whole `parse_external_string` idea. Maybe I lack some of the background knowledge here, but I don't see why we choose not to 1. parse external arguments the same way as internal ones 2. treat them literally at runtime if necessary Tests: +1 --- crates/nu-parser/src/parser.rs | 26 ++++++++++++++++++++++++++ tests/repl/test_parser.rs | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7cf2b12ba9..7c6364363a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -264,6 +264,10 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre quote_char: u8, escaped: bool, }, + Parenthesized { + from: usize, + depth: usize, + }, } // Find the spans of parts of the string that can be parsed as their own strings for // concatenation. @@ -315,6 +319,15 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre } state = State::BackTickQuote { from: index } } + b'(' => { + if index != *from { + spans.push(make_span(*from, index)) + } + state = State::Parenthesized { + from: index, + depth: 1, + } + } // Continue to consume _ => (), }, @@ -343,6 +356,18 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre state = State::Bare { from: index + 1 }; } } + State::Parenthesized { from, depth } => { + if ch == b')' { + if *depth == 1 { + spans.push(make_span(*from, index + 1)); + state = State::Bare { from: index + 1 }; + } else { + *depth -= 1; + } + } else if ch == b'(' { + *depth += 1; + } + } } index += 1; } @@ -351,6 +376,7 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre match state { State::Bare { from } | State::Quote { from, .. } + | State::Parenthesized { from, .. } | State::BackTickQuote { from, .. } => { if from < contents.len() { spans.push(make_span(from, contents.len())); diff --git a/tests/repl/test_parser.rs b/tests/repl/test_parser.rs index 0cc4bc4cfe..f532a74a4e 100644 --- a/tests/repl/test_parser.rs +++ b/tests/repl/test_parser.rs @@ -1027,3 +1027,12 @@ fn not_panic_with_recursive_call() { ); assert!(result.status.success()); } + +// https://github.com/nushell/nushell/issues/16040 +#[test] +fn external_argument_with_subexpressions() -> TestResult { + run_test(r#"^echo foo( ('bar') | $in ++ 'baz' )"#, "foobarbaz")?; + run_test(r#"^echo foo( 'bar' )('baz')"#, "foobarbaz")?; + run_test(r#"^echo ")('foo')(""#, ")('foo')(")?; + fail_test(r#"^echo foo( 'bar'"#, "Unexpected end of code") +}