mirror of
https://github.com/nushell/nushell.git
synced 2025-05-09 12:34:26 +02:00
fix(parser): mixed side effects of different choices in parse_oneof
(#14912)
# Description This PR fixes #14784. <img width="384" alt="image" src="https://github.com/user-attachments/assets/aac063a0-645d-4adb-a399-525bdb004999" /> Also fixes the related behavior of lsp: completion won't work in match/else blocks, because: 1. truncation in completion causes unmatched `{`, thus a parse error. 2. the parse error further leads to a state where the whole block expression marked as garbage <img width="453" alt="image" src="https://github.com/user-attachments/assets/aaf86ccc-646e-4b91-bb27-4b1737100ff2" /> Related PR: #14856, @tmillr I don't have any background knowledge of those `propagate_error`, @sgvictorino you may want to review this. # User-Facing Changes # Tests + Formatting # After Submitting
This commit is contained in:
parent
6be42d94d9
commit
3836da0cf1
@ -781,42 +781,57 @@ fn parse_oneof(
|
|||||||
possible_shapes: &Vec<SyntaxShape>,
|
possible_shapes: &Vec<SyntaxShape>,
|
||||||
multispan: bool,
|
multispan: bool,
|
||||||
) -> Expression {
|
) -> Expression {
|
||||||
|
let starting_spans_idx = *spans_idx;
|
||||||
|
let mut best_guess = None;
|
||||||
|
let mut best_guess_errors = Vec::new();
|
||||||
|
let mut max_first_error_offset = 0;
|
||||||
|
let mut propagate_error = false;
|
||||||
for shape in possible_shapes {
|
for shape in possible_shapes {
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
|
*spans_idx = starting_spans_idx;
|
||||||
let value = match multispan {
|
let value = match multispan {
|
||||||
true => parse_multispan_value(working_set, spans, spans_idx, shape),
|
true => parse_multispan_value(working_set, spans, spans_idx, shape),
|
||||||
false => parse_value(working_set, spans[*spans_idx], shape),
|
false => parse_value(working_set, spans[*spans_idx], shape),
|
||||||
};
|
};
|
||||||
|
|
||||||
if starting_error_count == working_set.parse_errors.len() {
|
let new_errors = working_set.parse_errors[starting_error_count..].to_vec();
|
||||||
|
// no new errors found means success
|
||||||
|
let Some(first_error_offset) = new_errors.iter().map(|e| e.span().start).min() else {
|
||||||
return value;
|
return value;
|
||||||
}
|
|
||||||
|
|
||||||
// while trying the possible shapes, ignore Expected type errors
|
|
||||||
// unless they're inside a block, closure, or expression
|
|
||||||
let propagate_error = match working_set.parse_errors.last() {
|
|
||||||
Some(ParseError::Expected(_, error_span))
|
|
||||||
| Some(ParseError::ExpectedWithStringMsg(_, error_span)) => {
|
|
||||||
matches!(
|
|
||||||
shape,
|
|
||||||
SyntaxShape::Block | SyntaxShape::Closure(_) | SyntaxShape::Expression
|
|
||||||
) && *error_span != spans[*spans_idx]
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
};
|
};
|
||||||
if !propagate_error {
|
|
||||||
working_set.parse_errors.truncate(starting_error_count);
|
if first_error_offset > max_first_error_offset {
|
||||||
|
// while trying the possible shapes, ignore Expected type errors
|
||||||
|
// unless they're inside a block, closure, or expression
|
||||||
|
propagate_error = match working_set.parse_errors.last() {
|
||||||
|
Some(ParseError::Expected(_, error_span))
|
||||||
|
| Some(ParseError::ExpectedWithStringMsg(_, error_span)) => {
|
||||||
|
matches!(
|
||||||
|
shape,
|
||||||
|
SyntaxShape::Block | SyntaxShape::Closure(_) | SyntaxShape::Expression
|
||||||
|
) && *error_span != spans[*spans_idx]
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
max_first_error_offset = first_error_offset;
|
||||||
|
best_guess = Some(value);
|
||||||
|
best_guess_errors = new_errors;
|
||||||
}
|
}
|
||||||
|
working_set.parse_errors.truncate(starting_error_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if working_set.parse_errors.is_empty() {
|
// if best_guess results in new errors further than current span, then accept it
|
||||||
|
// or propagate_error is marked as true for it
|
||||||
|
if max_first_error_offset > spans[starting_spans_idx].start || propagate_error {
|
||||||
|
working_set.parse_errors.extend(best_guess_errors);
|
||||||
|
best_guess.expect("best_guess should not be None here!")
|
||||||
|
} else {
|
||||||
working_set.error(ParseError::ExpectedWithStringMsg(
|
working_set.error(ParseError::ExpectedWithStringMsg(
|
||||||
format!("one of a list of accepted shapes: {possible_shapes:?}"),
|
format!("one of a list of accepted shapes: {possible_shapes:?}"),
|
||||||
spans[*spans_idx],
|
spans[starting_spans_idx],
|
||||||
));
|
));
|
||||||
|
Expression::garbage(working_set, spans[starting_spans_idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression::garbage(working_set, spans[*spans_idx])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_multispan_value(
|
pub fn parse_multispan_value(
|
||||||
|
@ -2426,7 +2426,7 @@ mod input_types {
|
|||||||
add_declarations(&mut engine_state);
|
add_declarations(&mut engine_state);
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
parse(
|
let block = parse(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
None,
|
None,
|
||||||
b"if false { 'a' } else { $foo }",
|
b"if false { 'a' } else { $foo }",
|
||||||
@ -2437,6 +2437,30 @@ mod input_types {
|
|||||||
working_set.parse_errors.first(),
|
working_set.parse_errors.first(),
|
||||||
Some(ParseError::VariableNotFound(_, _))
|
Some(ParseError::VariableNotFound(_, _))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let element = &block
|
||||||
|
.pipelines
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.elements
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.expr;
|
||||||
|
let Expr::Call(call) = &element.expr else {
|
||||||
|
eprintln!("{:?}", element.expr);
|
||||||
|
panic!("Expected Expr::Call");
|
||||||
|
};
|
||||||
|
let Expr::Keyword(else_kwd) = &call
|
||||||
|
.arguments
|
||||||
|
.get(2)
|
||||||
|
.expect("This call of `if` should have 3 arguments")
|
||||||
|
.expr()
|
||||||
|
.unwrap()
|
||||||
|
.expr
|
||||||
|
else {
|
||||||
|
panic!("Expected Expr::Keyword");
|
||||||
|
};
|
||||||
|
assert!(!matches!(else_kwd.expr.expr, Expr::Garbage))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2597,7 +2621,7 @@ mod record {
|
|||||||
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);
|
||||||
let block = parse(&mut working_set, None, expr, false);
|
let block = parse(&mut working_set, None, expr, false);
|
||||||
assert!(working_set.parse_errors.first().is_none());
|
assert!(working_set.parse_errors.is_empty());
|
||||||
let pipeline_el_expr = &block
|
let pipeline_el_expr = &block
|
||||||
.pipelines
|
.pipelines
|
||||||
.first()
|
.first()
|
||||||
|
Loading…
Reference in New Issue
Block a user