Skip to content

Commit abce923

Browse files
committed
fix(parser): skip eval_const if parsing errors detected to avoid panic
1 parent 2c7ab6e commit abce923

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

crates/nu-parser/tests/test_parser.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use nu_protocol::{
66
};
77
use rstest::rstest;
88

9-
use mock::{Alias, AttrEcho, Def, Let, Mut, ToCustom};
9+
use mock::{Alias, AttrEcho, Const, Def, IfMocked, Let, Mut, ToCustom};
1010

1111
fn test_int(
1212
test_tag: &str, // name of sub-test
@@ -784,6 +784,38 @@ pub fn parse_attributes_external_alias() {
784784
assert!(parse_error.contains("Encountered error during parse-time evaluation"));
785785
}
786786

787+
#[test]
788+
pub fn parse_if_in_const_expression() {
789+
// https://github.com/nushell/nushell/issues/15321
790+
let engine_state = EngineState::new();
791+
let mut working_set = StateWorkingSet::new(&engine_state);
792+
793+
working_set.add_decl(Box::new(Const));
794+
working_set.add_decl(Box::new(Def));
795+
working_set.add_decl(Box::new(IfMocked));
796+
797+
let source = b"const foo = if t";
798+
let _ = parse(&mut working_set, None, source, false);
799+
800+
assert!(!working_set.parse_errors.is_empty());
801+
let ParseError::MissingPositional(error, _, _) = &working_set.parse_errors[0] else {
802+
panic!("Expected MissingPositional");
803+
};
804+
805+
assert!(error.contains("cond"));
806+
807+
working_set.parse_errors = Vec::new();
808+
let source = b"def a [n= (if ]";
809+
let _ = parse(&mut working_set, None, source, false);
810+
811+
assert!(!working_set.parse_errors.is_empty());
812+
let ParseError::UnexpectedEof(error, _) = &working_set.parse_errors[0] else {
813+
panic!("Expected UnexpectedEof");
814+
};
815+
816+
assert!(error.contains(")"));
817+
}
818+
787819
fn test_external_call(input: &str, tag: &str, f: impl FnOnce(&Expression, &[ExternalArgument])) {
788820
let engine_state = EngineState::new();
789821
let mut working_set = StateWorkingSet::new(&engine_state);
@@ -1969,6 +2001,51 @@ mod mock {
19692001
engine::Call, Category, IntoPipelineData, PipelineData, ShellError, Type, Value,
19702002
};
19712003

2004+
#[derive(Clone)]
2005+
pub struct Const;
2006+
2007+
impl Command for Const {
2008+
fn name(&self) -> &str {
2009+
"const"
2010+
}
2011+
2012+
fn description(&self) -> &str {
2013+
"Create a parse-time constant."
2014+
}
2015+
2016+
fn signature(&self) -> nu_protocol::Signature {
2017+
Signature::build("const")
2018+
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
2019+
.allow_variants_without_examples(true)
2020+
.required("const_name", SyntaxShape::VarWithOptType, "Constant name.")
2021+
.required(
2022+
"initial_value",
2023+
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
2024+
"Equals sign followed by constant value.",
2025+
)
2026+
.category(Category::Core)
2027+
}
2028+
2029+
fn run(
2030+
&self,
2031+
_engine_state: &EngineState,
2032+
_stack: &mut Stack,
2033+
_call: &Call,
2034+
_input: PipelineData,
2035+
) -> Result<PipelineData, ShellError> {
2036+
todo!()
2037+
}
2038+
2039+
fn run_const(
2040+
&self,
2041+
_working_set: &StateWorkingSet,
2042+
_call: &Call,
2043+
_input: PipelineData,
2044+
) -> Result<PipelineData, ShellError> {
2045+
Ok(PipelineData::empty())
2046+
}
2047+
}
2048+
19722049
#[derive(Clone)]
19732050
pub struct Let;
19742051

@@ -2422,6 +2499,19 @@ mod mock {
24222499
) -> Result<PipelineData, ShellError> {
24232500
todo!()
24242501
}
2502+
2503+
fn is_const(&self) -> bool {
2504+
true
2505+
}
2506+
2507+
fn run_const(
2508+
&self,
2509+
_working_set: &StateWorkingSet,
2510+
_call: &Call,
2511+
_input: PipelineData,
2512+
) -> Result<PipelineData, ShellError> {
2513+
panic!("Should not be called!")
2514+
}
24252515
}
24262516
}
24272517

crates/nu-protocol/src/eval_const.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,19 @@ impl Eval for EvalConst {
510510
) -> Result<Value, ShellError> {
511511
// TODO: Allow debugging const eval
512512
let block = working_set.get_block(block_id);
513+
514+
// If parsing errors exist in the block, don't bother to evaluate it.
515+
if let Some(block_span) = block.span {
516+
if working_set
517+
.parse_errors
518+
.iter()
519+
.any(|error| block_span.contains_span(error.span()))
520+
{
521+
return Err(ShellError::NotAConstant { span });
522+
}
523+
} else if !working_set.parse_errors.is_empty() {
524+
return Err(ShellError::NotAConstant { span });
525+
};
513526
eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
514527
}
515528

0 commit comments

Comments
 (0)