mirror of
https://github.com/nushell/nushell.git
synced 2025-05-06 07:52:57 +00:00
fix(parser): skip eval_const if parsing errors detected to avoid panic (#15364)
Fixes #14972 #15321 #14706 # Description Early returns `NotAConstant` if parsing errors exist in the subexpression. I'm not sure when the span of a block will be None, and whether there're better ways to handle none block spans, like a more suitable ShellError type. # User-Facing Changes # Tests + Formatting +1, but possibly not the easiest way to do it. # After Submitting
This commit is contained in:
parent
55e05be0d8
commit
02fcc485fb
@ -6,7 +6,7 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
use mock::{Alias, AttrEcho, Def, Let, Mut, ToCustom};
|
use mock::{Alias, AttrEcho, Const, Def, IfMocked, Let, Mut, ToCustom};
|
||||||
|
|
||||||
fn test_int(
|
fn test_int(
|
||||||
test_tag: &str, // name of sub-test
|
test_tag: &str, // name of sub-test
|
||||||
@ -784,6 +784,38 @@ pub fn parse_attributes_external_alias() {
|
|||||||
assert!(parse_error.contains("Encountered error during parse-time evaluation"));
|
assert!(parse_error.contains("Encountered error during parse-time evaluation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn parse_if_in_const_expression() {
|
||||||
|
// https://github.com/nushell/nushell/issues/15321
|
||||||
|
let engine_state = EngineState::new();
|
||||||
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
|
working_set.add_decl(Box::new(Const));
|
||||||
|
working_set.add_decl(Box::new(Def));
|
||||||
|
working_set.add_decl(Box::new(IfMocked));
|
||||||
|
|
||||||
|
let source = b"const foo = if t";
|
||||||
|
let _ = parse(&mut working_set, None, source, false);
|
||||||
|
|
||||||
|
assert!(!working_set.parse_errors.is_empty());
|
||||||
|
let ParseError::MissingPositional(error, _, _) = &working_set.parse_errors[0] else {
|
||||||
|
panic!("Expected MissingPositional");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(error.contains("cond"));
|
||||||
|
|
||||||
|
working_set.parse_errors = Vec::new();
|
||||||
|
let source = b"def a [n= (if ]";
|
||||||
|
let _ = parse(&mut working_set, None, source, false);
|
||||||
|
|
||||||
|
assert!(!working_set.parse_errors.is_empty());
|
||||||
|
let ParseError::UnexpectedEof(error, _) = &working_set.parse_errors[0] else {
|
||||||
|
panic!("Expected UnexpectedEof");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(error.contains(")"));
|
||||||
|
}
|
||||||
|
|
||||||
fn test_external_call(input: &str, tag: &str, f: impl FnOnce(&Expression, &[ExternalArgument])) {
|
fn test_external_call(input: &str, tag: &str, f: impl FnOnce(&Expression, &[ExternalArgument])) {
|
||||||
let engine_state = EngineState::new();
|
let engine_state = EngineState::new();
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
@ -1969,6 +2001,51 @@ mod mock {
|
|||||||
engine::Call, Category, IntoPipelineData, PipelineData, ShellError, Type, Value,
|
engine::Call, Category, IntoPipelineData, PipelineData, ShellError, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Const;
|
||||||
|
|
||||||
|
impl Command for Const {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"const"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Create a parse-time constant."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("const")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required("const_name", SyntaxShape::VarWithOptType, "Constant name.")
|
||||||
|
.required(
|
||||||
|
"initial_value",
|
||||||
|
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||||
|
"Equals sign followed by constant value.",
|
||||||
|
)
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
_working_set: &StateWorkingSet,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Let;
|
pub struct Let;
|
||||||
|
|
||||||
@ -2422,6 +2499,19 @@ mod mock {
|
|||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
_working_set: &StateWorkingSet,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
panic!("Should not be called!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1140,6 +1140,25 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
|
|||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// TODO: Get rid of this error by moving the check before evaluation
|
||||||
|
///
|
||||||
|
/// Tried evaluating of a subexpression with parsing error
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// Fix the parsing error first.
|
||||||
|
#[error("Found parsing error in expression.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::parse_error_in_constant),
|
||||||
|
help(
|
||||||
|
"This expression is supposed to be evaluated into a constant, which means error-free."
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
ParseErrorInConstant {
|
||||||
|
#[label("Parsing error detected in expression")]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
/// Tried assigning non-constant value to a constant
|
/// Tried assigning non-constant value to a constant
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
@ -508,6 +508,14 @@ impl Eval for EvalConst {
|
|||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
|
// If parsing errors exist in the subexpression, don't bother to evaluate it.
|
||||||
|
if working_set
|
||||||
|
.parse_errors
|
||||||
|
.iter()
|
||||||
|
.any(|error| span.contains_span(error.span()))
|
||||||
|
{
|
||||||
|
return Err(ShellError::ParseErrorInConstant { span });
|
||||||
|
}
|
||||||
// TODO: Allow debugging const eval
|
// TODO: Allow debugging const eval
|
||||||
let block = working_set.get_block(block_id);
|
let block = working_set.get_block(block_id);
|
||||||
eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
|
eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user