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
This commit is contained in:
zc he
2025-08-07 03:17:58 +08:00
committed by GitHub
parent 0e3ca7b355
commit 4e56cd5fc4
2 changed files with 35 additions and 0 deletions

View File

@ -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()));

View File

@ -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")
}