From 61028868a879977145fcf98ae378572c6e9b9e4d Mon Sep 17 00:00:00 2001 From: Beta Ziliani Date: Thu, 12 Dec 2024 12:13:23 -0300 Subject: [PATCH] Precise error location in UNRECOGNIZED by attaching trivia to the expected nonterminal (#1172) Fixes #841 In examples like the following: ``` // some trivia unexpected ``` The parsing function was returning a terminal with `UNRECOGNIZED` kind and the entire text inside. Since a terminal cannot have trivia attached, we have to change the returning value to be a nonterminal. But which one? For a moment I thought of a new specific `UNRECOGNIZED` kind, but after discussing with Omar, it was clear that the better option was to return the expected one: if we are parsing a `Statement`, then return a `Statement` with the trivia and the `UNRECOGNIZED` terminal at the end. This is realized in the `NoMatch` structure with a new field for the optional expected nonterminal kind, which is essentially attached in the `ParserResult::with_kind` method. The commit history contains a previous attempt in which the trivia was cached within the `NoMatch` struct. This added unnecessary complexity, so I decided against it. **Note1:** In a couple of places the code will use a `ParserResult::no_match()`. Noting there was a `ParseResult::default()` function, I decided to use it instead, but I'm not sure if this is the expected use of the default function. **Note2:** I see that the error is one off at the end, probably accounting EOF? But was already the case before, so there's no real change there. --- .changeset/smart-cooks-suffer.md | 5 ++ .../crate/src/runtime/parser/lexer/mod.rs | 4 +- .../parser/parser_support/choice_helper.rs | 2 +- .../parser/parser_support/parser_function.rs | 34 +++++++- .../parser/parser_support/parser_result.rs | 22 +++-- .../runtime/parser/parser_support/recovery.rs | 2 +- .../parser_support/repetition_helper.rs | 9 +- .../parser/parser_support/separated_helper.rs | 2 +- .../codegen/precedence_parser_definition.rs | 4 +- crates/metaslang/cst/generated/public_api.txt | 1 + crates/metaslang/cst/src/text_index.rs | 14 ++-- .../src/generated/parser/generated/parser.rs | 84 +++++++++---------- .../crate/src/generated/parser/lexer/mod.rs | 4 +- .../parser/parser_support/choice_helper.rs | 2 +- .../parser/parser_support/parser_function.rs | 34 +++++++- .../parser/parser_support/parser_result.rs | 22 +++-- .../parser/parser_support/recovery.rs | 2 +- .../parser_support/repetition_helper.rs | 9 +- .../parser/parser_support/separated_helper.rs | 2 +- .../generated/0.4.11-failure.yml | 19 +++-- .../generated/0.4.25-failure.yml | 14 ++-- .../generated/0.4.25-failure.yml | 14 ++-- .../try_catch/generated/0.4.11-failure.yml | 12 ++- .../with_var/generated/0.5.0-failure.yml | 14 ++-- .../generated/0.6.0-failure.yml | 14 ++-- .../src/generated/parser/generated/parser.rs | 12 +-- .../crate/src/generated/parser/lexer/mod.rs | 4 +- .../parser/parser_support/choice_helper.rs | 2 +- .../parser/parser_support/parser_function.rs | 34 +++++++- .../parser/parser_support/parser_result.rs | 22 +++-- .../parser/parser_support/recovery.rs | 2 +- .../parser_support/repetition_helper.rs | 9 +- .../parser/parser_support/separated_helper.rs | 2 +- 33 files changed, 292 insertions(+), 140 deletions(-) create mode 100644 .changeset/smart-cooks-suffer.md diff --git a/.changeset/smart-cooks-suffer.md b/.changeset/smart-cooks-suffer.md new file mode 100644 index 0000000000..2c46a5040c --- /dev/null +++ b/.changeset/smart-cooks-suffer.md @@ -0,0 +1,5 @@ +--- +"@nomicfoundation/slang": minor +--- + +Improved error recovery, where leading trivia are always parsed and included before an erroneous terminal. diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/lexer/mod.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/lexer/mod.rs index 71d28c520b..156ce5583d 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/lexer/mod.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/lexer/mod.rs @@ -94,7 +94,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(start); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); @@ -129,7 +129,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(restore); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); children.push(Edge::anonymous(Node::terminal( diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/choice_helper.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/choice_helper.rs index d436ce7732..a514e7b89e 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/choice_helper.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/choice_helper.rs @@ -21,7 +21,7 @@ pub struct ChoiceHelper { impl ChoiceHelper { pub fn new(input: &mut ParserContext<'_>) -> Self { Self { - result: ParserResult::no_match(vec![]), + result: ParserResult::default(), start_position: input.mark(), recovered_errors: vec![], last_progress: input.position(), diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_function.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_function.rs index f450a3a6f2..f2ab664108 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_function.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_function.rs @@ -57,16 +57,44 @@ where ParserResult::PrattOperatorMatch(..) => unreachable!("PrattOperatorMatch is internal"), ParserResult::NoMatch(no_match) => { + // Parse leading trivia (see #1172 for details). One could argue that trivia should be parsed + // just once, and returned in the `NoMatch` structure. However, in rules like (This | That), + // trivia is already parsed twice, one for each branch. And there's a good reason: each branch might + // accept different trivia, so it's not clear what the combination of the two rules should return in a + // NoMatch. Therefore, we just parse it again. Note that trivia is anyway cached by the parser (#1119). + let mut trivia_nodes = if let ParserResult::Match(matched) = + Lexer::leading_trivia(parser, &mut stream) + { + matched.nodes + } else { + vec![] + }; + + let mut start = TextIndex::ZERO; + for edge in &trivia_nodes { + if let Node::Terminal(terminal) = &edge.node { + if terminal.kind.is_valid() { + start.advance_str(terminal.text.as_str()); + } + } + } + let input = &input[start.utf8..]; let kind = if input.is_empty() { TerminalKind::MISSING } else { TerminalKind::UNRECOGNIZED }; - + let node = Node::terminal(kind, input.to_string()); + let tree = if no_match.kind.is_none() || start.utf8 == 0 { + node + } else { + trivia_nodes.push(Edge::anonymous(node)); + Node::nonterminal(no_match.kind.unwrap(), trivia_nodes) + }; ParseOutput { - parse_tree: Node::terminal(kind, input.to_string()), + parse_tree: tree, errors: vec![ParseError::new( - TextIndex::ZERO..input.into(), + start..start + input.into(), no_match.expected_terminals, )], } diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_result.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_result.rs index 1ec986602b..2e091daf1e 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_result.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/parser_result.rs @@ -16,6 +16,7 @@ pub enum ParserResult { impl Default for ParserResult { fn default() -> Self { Self::NoMatch(NoMatch { + kind: None, expected_terminals: vec![], }) } @@ -34,13 +35,13 @@ impl ParserResult { ParserResult::IncompleteMatch(IncompleteMatch::new(nodes, expected_terminals)) } - /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(vec![])`. + /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(None, vec![])`. pub fn disabled() -> Self { - Self::no_match(vec![]) + Self::no_match(None, vec![]) } - pub fn no_match(expected_terminals: Vec) -> Self { - ParserResult::NoMatch(NoMatch::new(expected_terminals)) + pub fn no_match(kind: Option, expected_terminals: Vec) -> Self { + ParserResult::NoMatch(NoMatch::new(kind, expected_terminals)) } #[must_use] @@ -61,7 +62,9 @@ impl ParserResult { nodes: vec![Edge::anonymous(Node::nonterminal(new_kind, skipped.nodes))], ..skipped }), - ParserResult::NoMatch(_) => self, + ParserResult::NoMatch(no_match) => { + ParserResult::no_match(Some(new_kind), no_match.expected_terminals) + } ParserResult::PrattOperatorMatch(_) => { unreachable!("PrattOperatorMatch cannot be converted to a nonterminal") } @@ -240,13 +243,18 @@ impl IncompleteMatch { #[derive(PartialEq, Eq, Clone, Debug)] pub struct NoMatch { + /// The nonterminal kind this match is coming from + pub kind: Option, /// Terminals that would have allowed for more progress. Collected for the purposes of error reporting. pub expected_terminals: Vec, } impl NoMatch { - pub fn new(expected_terminals: Vec) -> Self { - Self { expected_terminals } + pub fn new(kind: Option, expected_terminals: Vec) -> Self { + Self { + kind, + expected_terminals, + } } } diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/recovery.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/recovery.rs index cec55a393c..9885e723b3 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/recovery.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/recovery.rs @@ -105,7 +105,7 @@ impl ParserResult { ParseResultKind::Incomplete => { ParserResult::incomplete_match(nodes, expected_terminals) } - ParseResultKind::NoMatch => ParserResult::no_match(expected_terminals), + ParseResultKind::NoMatch => ParserResult::no_match(None, expected_terminals), } } } diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/repetition_helper.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/repetition_helper.rs index e4bcf1433d..3d612e4fe3 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/repetition_helper.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/repetition_helper.rs @@ -26,7 +26,10 @@ impl RepetitionHelper { // Couldn't get a full match but we allow 0 items - return an empty match // so the parse is considered valid but note the expected terminals - ParserResult::NoMatch(NoMatch { expected_terminals }) if MIN_COUNT == 0 => { + ParserResult::NoMatch(NoMatch { + kind: _, + expected_terminals, + }) if MIN_COUNT == 0 => { return ParserResult::r#match(vec![], expected_terminals); } // Don't try repeating if we don't have a full match and we require at least one @@ -60,7 +63,9 @@ impl RepetitionHelper { ParserResult::IncompleteMatch(IncompleteMatch { expected_terminals, .. }) - | ParserResult::NoMatch(NoMatch { expected_terminals }), + | ParserResult::NoMatch(NoMatch { + expected_terminals, .. + }), ) => { input.rewind(save); running.expected_terminals = expected_terminals; diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/separated_helper.rs b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/separated_helper.rs index e1455a8155..280d89c8cf 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/separated_helper.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/parser/parser_support/separated_helper.rs @@ -89,7 +89,7 @@ impl SeparatedHelper { } ParserResult::NoMatch(no_match) => { return if accum.is_empty() { - ParserResult::no_match(no_match.expected_terminals) + ParserResult::no_match(None, no_match.expected_terminals) } else { ParserResult::incomplete_match(accum, no_match.expected_terminals) }; diff --git a/crates/codegen/runtime/generator/src/parser/codegen/precedence_parser_definition.rs b/crates/codegen/runtime/generator/src/parser/codegen/precedence_parser_definition.rs index 30c11f90a3..b917749aaa 100644 --- a/crates/codegen/runtime/generator/src/parser/codegen/precedence_parser_definition.rs +++ b/crates/codegen/runtime/generator/src/parser/codegen/precedence_parser_definition.rs @@ -49,9 +49,9 @@ impl PrecedenceParserDefinitionCodegen for PrecedenceParserDefinitionRef { [inner @ cst::Edge { node: cst::Node::Nonterminal(node), .. }] if node.kind == NonterminalKind::#op_nonterminal_name => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } }; diff --git a/crates/metaslang/cst/generated/public_api.txt b/crates/metaslang/cst/generated/public_api.txt index 26c8767047..83370fcc7c 100644 --- a/crates/metaslang/cst/generated/public_api.txt +++ b/crates/metaslang/cst/generated/public_api.txt @@ -242,6 +242,7 @@ pub metaslang_cst::text_index::TextIndex::utf8: usize impl metaslang_cst::text_index::TextIndex pub const metaslang_cst::text_index::TextIndex::ZERO: metaslang_cst::text_index::TextIndex pub fn metaslang_cst::text_index::TextIndex::advance(&mut self, c: char, next: core::option::Option<&char>) +pub fn metaslang_cst::text_index::TextIndex::advance_str(&mut self, text: &str) impl core::clone::Clone for metaslang_cst::text_index::TextIndex pub fn metaslang_cst::text_index::TextIndex::clone(&self) -> metaslang_cst::text_index::TextIndex impl core::cmp::Eq for metaslang_cst::text_index::TextIndex diff --git a/crates/metaslang/cst/src/text_index.rs b/crates/metaslang/cst/src/text_index.rs index c107c4f191..18ed1b4dd8 100644 --- a/crates/metaslang/cst/src/text_index.rs +++ b/crates/metaslang/cst/src/text_index.rs @@ -40,6 +40,14 @@ impl TextIndex { } } } + + pub fn advance_str(&mut self, text: &str) { + let mut iter = text.chars().peekable(); + while let Some(c) = iter.next() { + let n = iter.peek(); + self.advance(c, n); + } + } } impl PartialOrd for TextIndex { @@ -63,11 +71,7 @@ impl Display for TextIndex { impl> From for TextIndex { fn from(s: T) -> Self { let mut result = Self::ZERO; - let mut iter = s.as_ref().chars().peekable(); - while let Some(c) = iter.next() { - let n = iter.peek(); - result.advance(c, n); - } + result.advance_str(s.as_ref()); result } } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/generated/parser.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/generated/parser.rs index e887cdb596..de10be92a5 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/generated/parser.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/generated/parser.rs @@ -254,9 +254,9 @@ impl Parser { }] if node.kind == NonterminalKind::AdditiveExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -301,9 +301,9 @@ impl Parser { }] if node.kind == NonterminalKind::AndExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -371,9 +371,9 @@ impl Parser { }] if node.kind == NonterminalKind::ArrayTypeName => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -476,9 +476,9 @@ impl Parser { }] if node.kind == NonterminalKind::AssignmentExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -499,9 +499,9 @@ impl Parser { }] if node.kind == NonterminalKind::BitwiseAndExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -522,9 +522,9 @@ impl Parser { }] if node.kind == NonterminalKind::BitwiseOrExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -545,9 +545,9 @@ impl Parser { }] if node.kind == NonterminalKind::BitwiseXorExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -646,9 +646,9 @@ impl Parser { }] if node.kind == NonterminalKind::CallOptionsExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -727,9 +727,9 @@ impl Parser { }] if node.kind == NonterminalKind::ComparisonExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -750,9 +750,9 @@ impl Parser { }] if node.kind == NonterminalKind::ConditionalExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -1320,9 +1320,9 @@ impl Parser { }] if node.kind == NonterminalKind::EqualityExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -1625,9 +1625,9 @@ impl Parser { }] if node.kind == NonterminalKind::ExponentiationExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -2639,9 +2639,9 @@ impl Parser { }] if node.kind == NonterminalKind::FunctionCallExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -3082,9 +3082,9 @@ impl Parser { }] if node.kind == NonterminalKind::IndexAccessExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -3375,9 +3375,9 @@ impl Parser { }] if node.kind == NonterminalKind::MemberAccessExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -3467,9 +3467,9 @@ impl Parser { }] if node.kind == NonterminalKind::MultiplicativeExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -3705,9 +3705,9 @@ impl Parser { }] if node.kind == NonterminalKind::OrExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -3932,9 +3932,9 @@ impl Parser { }] if node.kind == NonterminalKind::PostfixExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -4004,9 +4004,9 @@ impl Parser { }] if node.kind == NonterminalKind::PrefixExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -4192,9 +4192,9 @@ impl Parser { }] if node.kind == NonterminalKind::ShiftExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -6482,9 +6482,9 @@ impl Parser { }] if node.kind == NonterminalKind::YulFunctionCallExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/lexer/mod.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/lexer/mod.rs index 02bbe2b817..c648756daf 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/lexer/mod.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/lexer/mod.rs @@ -96,7 +96,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(start); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); @@ -131,7 +131,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(restore); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); children.push(Edge::anonymous(Node::terminal( diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs index 46651ef877..2e14c5a35f 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs @@ -23,7 +23,7 @@ pub struct ChoiceHelper { impl ChoiceHelper { pub fn new(input: &mut ParserContext<'_>) -> Self { Self { - result: ParserResult::no_match(vec![]), + result: ParserResult::default(), start_position: input.mark(), recovered_errors: vec![], last_progress: input.position(), diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs index 420d19f53a..c9fc3e44e5 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs @@ -59,16 +59,44 @@ where ParserResult::PrattOperatorMatch(..) => unreachable!("PrattOperatorMatch is internal"), ParserResult::NoMatch(no_match) => { + // Parse leading trivia (see #1172 for details). One could argue that trivia should be parsed + // just once, and returned in the `NoMatch` structure. However, in rules like (This | That), + // trivia is already parsed twice, one for each branch. And there's a good reason: each branch might + // accept different trivia, so it's not clear what the combination of the two rules should return in a + // NoMatch. Therefore, we just parse it again. Note that trivia is anyway cached by the parser (#1119). + let mut trivia_nodes = if let ParserResult::Match(matched) = + Lexer::leading_trivia(parser, &mut stream) + { + matched.nodes + } else { + vec![] + }; + + let mut start = TextIndex::ZERO; + for edge in &trivia_nodes { + if let Node::Terminal(terminal) = &edge.node { + if terminal.kind.is_valid() { + start.advance_str(terminal.text.as_str()); + } + } + } + let input = &input[start.utf8..]; let kind = if input.is_empty() { TerminalKind::MISSING } else { TerminalKind::UNRECOGNIZED }; - + let node = Node::terminal(kind, input.to_string()); + let tree = if no_match.kind.is_none() || start.utf8 == 0 { + node + } else { + trivia_nodes.push(Edge::anonymous(node)); + Node::nonterminal(no_match.kind.unwrap(), trivia_nodes) + }; ParseOutput { - parse_tree: Node::terminal(kind, input.to_string()), + parse_tree: tree, errors: vec![ParseError::new( - TextIndex::ZERO..input.into(), + start..start + input.into(), no_match.expected_terminals, )], } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs index d26fe53f08..cfd9a8df7e 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs @@ -18,6 +18,7 @@ pub enum ParserResult { impl Default for ParserResult { fn default() -> Self { Self::NoMatch(NoMatch { + kind: None, expected_terminals: vec![], }) } @@ -36,13 +37,13 @@ impl ParserResult { ParserResult::IncompleteMatch(IncompleteMatch::new(nodes, expected_terminals)) } - /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(vec![])`. + /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(None, vec![])`. pub fn disabled() -> Self { - Self::no_match(vec![]) + Self::no_match(None, vec![]) } - pub fn no_match(expected_terminals: Vec) -> Self { - ParserResult::NoMatch(NoMatch::new(expected_terminals)) + pub fn no_match(kind: Option, expected_terminals: Vec) -> Self { + ParserResult::NoMatch(NoMatch::new(kind, expected_terminals)) } #[must_use] @@ -63,7 +64,9 @@ impl ParserResult { nodes: vec![Edge::anonymous(Node::nonterminal(new_kind, skipped.nodes))], ..skipped }), - ParserResult::NoMatch(_) => self, + ParserResult::NoMatch(no_match) => { + ParserResult::no_match(Some(new_kind), no_match.expected_terminals) + } ParserResult::PrattOperatorMatch(_) => { unreachable!("PrattOperatorMatch cannot be converted to a nonterminal") } @@ -242,13 +245,18 @@ impl IncompleteMatch { #[derive(PartialEq, Eq, Clone, Debug)] pub struct NoMatch { + /// The nonterminal kind this match is coming from + pub kind: Option, /// Terminals that would have allowed for more progress. Collected for the purposes of error reporting. pub expected_terminals: Vec, } impl NoMatch { - pub fn new(expected_terminals: Vec) -> Self { - Self { expected_terminals } + pub fn new(kind: Option, expected_terminals: Vec) -> Self { + Self { + kind, + expected_terminals, + } } } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs index f54ae45934..c49819e30f 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs @@ -107,7 +107,7 @@ impl ParserResult { ParseResultKind::Incomplete => { ParserResult::incomplete_match(nodes, expected_terminals) } - ParseResultKind::NoMatch => ParserResult::no_match(expected_terminals), + ParseResultKind::NoMatch => ParserResult::no_match(None, expected_terminals), } } } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs index a808e73796..a4fb3a2be5 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs @@ -28,7 +28,10 @@ impl RepetitionHelper { // Couldn't get a full match but we allow 0 items - return an empty match // so the parse is considered valid but note the expected terminals - ParserResult::NoMatch(NoMatch { expected_terminals }) if MIN_COUNT == 0 => { + ParserResult::NoMatch(NoMatch { + kind: _, + expected_terminals, + }) if MIN_COUNT == 0 => { return ParserResult::r#match(vec![], expected_terminals); } // Don't try repeating if we don't have a full match and we require at least one @@ -62,7 +65,9 @@ impl RepetitionHelper { ParserResult::IncompleteMatch(IncompleteMatch { expected_terminals, .. }) - | ParserResult::NoMatch(NoMatch { expected_terminals }), + | ParserResult::NoMatch(NoMatch { + expected_terminals, .. + }), ) => { input.rewind(save); running.expected_terminals = expected_terminals; diff --git a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs index bcc5dc64f0..eca96115d0 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs @@ -91,7 +91,7 @@ impl SeparatedHelper { } ParserResult::NoMatch(no_match) => { return if accum.is_empty() { - ParserResult::no_match(no_match.expected_terminals) + ParserResult::no_match(None, no_match.expected_terminals) } else { ParserResult::incomplete_match(accum, no_match.expected_terminals) }; diff --git a/crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/generated/0.4.11-failure.yml b/crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/generated/0.4.11-failure.yml index 53b662c853..02e2ecb360 100644 --- a/crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/generated/0.4.11-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/generated/0.4.11-failure.yml @@ -9,14 +9,19 @@ Source: > Errors: # 1 total - > Error: Expected ContractKeyword. - ╭─[crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/ContractDefinition/unicode_in_doc_comments/input.sol:4:1] │ - 1 │ ╭─▶ // ╒════════════════════════════════════════════════════════════════╕ - ┆ ┆ - 4 │ ├─▶ unexpected - │ │ - │ ╰──────────────── Error occurred here. + 4 │ unexpected + │ ─────┬───── + │ ╰─────── Error occurred here. ───╯ Tree: - - (UNRECOGNIZED): "// ╒══════════════════════════════════════════════..." # (0..489) + - (ContractDefinition): # "// ╒══════════════════════════════════════════════..." (0..489) + - (leading_trivia꞉ SingleLineComment): "// ╒══════════════════════════════════════════════..." # (0..201) + - (leading_trivia꞉ EndOfLine): "\n" # (201..202) + - (leading_trivia꞉ SingleLineComment): "// │ More Info: https://github.com/NomicFoundation..." # (202..275) + - (leading_trivia꞉ EndOfLine): "\n" # (275..276) + - (leading_trivia꞉ SingleLineComment): "// ╘══════════════════════════════════════════════..." # (276..477) + - (leading_trivia꞉ EndOfLine): "\n" # (477..478) + - (UNRECOGNIZED): "unexpected\n" # (478..489) diff --git a/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/generated/0.4.25-failure.yml b/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/generated/0.4.25-failure.yml index 9d82a9cbc4..6d35cb954c 100644 --- a/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/generated/0.4.25-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/generated/0.4.25-failure.yml @@ -7,13 +7,15 @@ Source: > Errors: # 1 total - > Error: Expected DoubleQuotedStringLiteral or SingleQuotedStringLiteral. - ╭─[crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_ascii/input.sol:2:1] │ - 1 │ ╭─▶ // Disallowed post 0.4.25 - 2 │ ├─▶ "\a" - │ │ - │ ╰────────── Error occurred here. + 2 │ "\a" + │ ──┬── + │ ╰──── Error occurred here. ───╯ Tree: - - (UNRECOGNIZED): '// Disallowed post 0.4.25\n"\a"\n' # (0..31) + - (StringLiteral): # '// Disallowed post 0.4.25\n"\a"\n' (0..31) + - (leading_trivia꞉ SingleLineComment): "// Disallowed post 0.4.25" # (0..25) + - (leading_trivia꞉ EndOfLine): "\n" # (25..26) + - (UNRECOGNIZED): '"\a"\n' # (26..31) diff --git a/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/generated/0.4.25-failure.yml b/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/generated/0.4.25-failure.yml index 687e39fa6e..e98c1d5e98 100644 --- a/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/generated/0.4.25-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/generated/0.4.25-failure.yml @@ -7,13 +7,15 @@ Source: > Errors: # 1 total - > Error: Expected DoubleQuotedStringLiteral or SingleQuotedStringLiteral. - ╭─[crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/StringLiteral/escape_arbitrary_unicode/input.sol:2:1] │ - 1 │ ╭─▶ // Disallowed post 0.4.25 - 2 │ ├─▶ "\✅" - │ │ - │ ╰─────────── Error occurred here. + 2 │ "\✅" + │ ──┬─── + │ ╰───── Error occurred here. ───╯ Tree: - - (UNRECOGNIZED): '// Disallowed post 0.4.25\n"\✅"\n' # (0..33) + - (StringLiteral): # '// Disallowed post 0.4.25\n"\✅"\n' (0..33) + - (leading_trivia꞉ SingleLineComment): "// Disallowed post 0.4.25" # (0..25) + - (leading_trivia꞉ EndOfLine): "\n" # (25..26) + - (UNRECOGNIZED): '"\✅"\n' # (26..33) diff --git a/crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/generated/0.4.11-failure.yml b/crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/generated/0.4.11-failure.yml index 112b08de0b..4f4bbd6c52 100644 --- a/crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/generated/0.4.11-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/generated/0.4.11-failure.yml @@ -12,9 +12,9 @@ Source: > Errors: # 1 total - > Error: Expected end of file. - ╭─[crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/TryStatement/try_catch/input.sol:4:1] │ - 1 │ ╭─▶ // Make sure that error recovery won't lead to misparsing + 4 │ ╭─▶ try foo() { ┆ ┆ 7 │ ├─▶ } │ │ @@ -22,4 +22,10 @@ Errors: # 1 total ───╯ Tree: - - (UNRECOGNIZED): "// Make sure that error recovery won't lead to mis..." # (0..171) + - (TryStatement): # "// Make sure that error recovery won't lead to mis..." (0..171) + - (leading_trivia꞉ SingleLineComment): "// Make sure that error recovery won't lead to mis..." # (0..57) + - (leading_trivia꞉ EndOfLine): "\n" # (57..58) + - (leading_trivia꞉ SingleLineComment): "// ambiguous function call options with the block ..." # (58..136) + - (leading_trivia꞉ EndOfLine): "\n" # (136..137) + - (leading_trivia꞉ EndOfLine): "\n" # (137..138) + - (UNRECOGNIZED): "try foo() {\n bar();\n} catch {\n}\n" # (138..171) diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/generated/0.5.0-failure.yml b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/generated/0.5.0-failure.yml index 35c47c54f3..12cc49290f 100644 --- a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/generated/0.5.0-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/generated/0.5.0-failure.yml @@ -7,13 +7,15 @@ Source: > Errors: # 1 total - > Error: Expected OpenParen. - ╭─[crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/with_var/input.sol:2:1] │ - 1 │ ╭─▶ // "var" should be disabled in "0.5.0": - 2 │ ├─▶ var (foo, bar) = rhs; - │ │ - │ ╰─────────────────────────── Error occurred here. + 2 │ var (foo, bar) = rhs; + │ ───────────┬────────── + │ ╰──────────── Error occurred here. ───╯ Tree: - - (UNRECOGNIZED): '// "var" should be disabled in "0.5.0":\nvar (foo, ...' # (0..62) + - (TupleDeconstructionStatement): # '// "var" should be disabled in "0.5.0":\nvar (foo, ...' (0..62) + - (leading_trivia꞉ SingleLineComment): '// "var" should be disabled in "0.5.0":' # (0..39) + - (leading_trivia꞉ EndOfLine): "\n" # (39..40) + - (UNRECOGNIZED): "var (foo, bar) = rhs;\n" # (40..62) diff --git a/crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/generated/0.6.0-failure.yml b/crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/generated/0.6.0-failure.yml index c588db9952..7f629684df 100644 --- a/crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/generated/0.6.0-failure.yml +++ b/crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/generated/0.6.0-failure.yml @@ -7,13 +7,15 @@ Source: > Errors: # 1 total - > Error: Expected end of file. - ╭─[crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/input.sol:1:1] + ╭─[crates/solidity/testing/snapshots/cst_output/UnnamedFunctionDefinition/constant_attribute/input.sol:2:1] │ - 1 │ ╭─▶ // Split into `view` and `pure` in 0.4.16 - 2 │ ├─▶ function () constant {} - │ │ - │ ╰───────────────────────────── Error occurred here. + 2 │ function () constant {} + │ ────────────┬─────────── + │ ╰───────────── Error occurred here. ───╯ Tree: - - (UNRECOGNIZED): "// Split into `view` and `pure` in 0.4.16\nfunction..." # (0..66) + - (UnnamedFunctionDefinition): # "// Split into `view` and `pure` in 0.4.16\nfunction..." (0..66) + - (leading_trivia꞉ SingleLineComment): "// Split into `view` and `pure` in 0.4.16" # (0..41) + - (leading_trivia꞉ EndOfLine): "\n" # (41..42) + - (UNRECOGNIZED): "function () constant {}\n" # (42..66) diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/generated/parser.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/generated/parser.rs index 9526734434..ef1e4ba83a 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/generated/parser.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/generated/parser.rs @@ -87,9 +87,9 @@ impl Parser { }] if node.kind == NonterminalKind::AdditionExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -237,9 +237,9 @@ impl Parser { }] if node.kind == NonterminalKind::MemberAccessExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } @@ -260,9 +260,9 @@ impl Parser { }] if node.kind == NonterminalKind::NegationExpression => { ParserResult::r#match(vec![inner.clone()], r#match.expected_terminals.clone()) } - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), }, - _ => ParserResult::no_match(vec![]), + _ => ParserResult::default(), } } diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/lexer/mod.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/lexer/mod.rs index 02bbe2b817..c648756daf 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/lexer/mod.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/lexer/mod.rs @@ -96,7 +96,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(start); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); @@ -131,7 +131,7 @@ pub(crate) trait Lexer { .is_some_and(|t| t.accepted_as(kind)) { input.set_position(restore); - return ParserResult::no_match(vec![kind]); + return ParserResult::no_match(None, vec![kind]); } let end = input.position(); children.push(Edge::anonymous(Node::terminal( diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs index 46651ef877..2e14c5a35f 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/choice_helper.rs @@ -23,7 +23,7 @@ pub struct ChoiceHelper { impl ChoiceHelper { pub fn new(input: &mut ParserContext<'_>) -> Self { Self { - result: ParserResult::no_match(vec![]), + result: ParserResult::default(), start_position: input.mark(), recovered_errors: vec![], last_progress: input.position(), diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs index 420d19f53a..c9fc3e44e5 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_function.rs @@ -59,16 +59,44 @@ where ParserResult::PrattOperatorMatch(..) => unreachable!("PrattOperatorMatch is internal"), ParserResult::NoMatch(no_match) => { + // Parse leading trivia (see #1172 for details). One could argue that trivia should be parsed + // just once, and returned in the `NoMatch` structure. However, in rules like (This | That), + // trivia is already parsed twice, one for each branch. And there's a good reason: each branch might + // accept different trivia, so it's not clear what the combination of the two rules should return in a + // NoMatch. Therefore, we just parse it again. Note that trivia is anyway cached by the parser (#1119). + let mut trivia_nodes = if let ParserResult::Match(matched) = + Lexer::leading_trivia(parser, &mut stream) + { + matched.nodes + } else { + vec![] + }; + + let mut start = TextIndex::ZERO; + for edge in &trivia_nodes { + if let Node::Terminal(terminal) = &edge.node { + if terminal.kind.is_valid() { + start.advance_str(terminal.text.as_str()); + } + } + } + let input = &input[start.utf8..]; let kind = if input.is_empty() { TerminalKind::MISSING } else { TerminalKind::UNRECOGNIZED }; - + let node = Node::terminal(kind, input.to_string()); + let tree = if no_match.kind.is_none() || start.utf8 == 0 { + node + } else { + trivia_nodes.push(Edge::anonymous(node)); + Node::nonterminal(no_match.kind.unwrap(), trivia_nodes) + }; ParseOutput { - parse_tree: Node::terminal(kind, input.to_string()), + parse_tree: tree, errors: vec![ParseError::new( - TextIndex::ZERO..input.into(), + start..start + input.into(), no_match.expected_terminals, )], } diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs index d26fe53f08..cfd9a8df7e 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/parser_result.rs @@ -18,6 +18,7 @@ pub enum ParserResult { impl Default for ParserResult { fn default() -> Self { Self::NoMatch(NoMatch { + kind: None, expected_terminals: vec![], }) } @@ -36,13 +37,13 @@ impl ParserResult { ParserResult::IncompleteMatch(IncompleteMatch::new(nodes, expected_terminals)) } - /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(vec![])`. + /// Whenever a parser didn't run because it's disabled due to versioning. Shorthand for `no_match(None, vec![])`. pub fn disabled() -> Self { - Self::no_match(vec![]) + Self::no_match(None, vec![]) } - pub fn no_match(expected_terminals: Vec) -> Self { - ParserResult::NoMatch(NoMatch::new(expected_terminals)) + pub fn no_match(kind: Option, expected_terminals: Vec) -> Self { + ParserResult::NoMatch(NoMatch::new(kind, expected_terminals)) } #[must_use] @@ -63,7 +64,9 @@ impl ParserResult { nodes: vec![Edge::anonymous(Node::nonterminal(new_kind, skipped.nodes))], ..skipped }), - ParserResult::NoMatch(_) => self, + ParserResult::NoMatch(no_match) => { + ParserResult::no_match(Some(new_kind), no_match.expected_terminals) + } ParserResult::PrattOperatorMatch(_) => { unreachable!("PrattOperatorMatch cannot be converted to a nonterminal") } @@ -242,13 +245,18 @@ impl IncompleteMatch { #[derive(PartialEq, Eq, Clone, Debug)] pub struct NoMatch { + /// The nonterminal kind this match is coming from + pub kind: Option, /// Terminals that would have allowed for more progress. Collected for the purposes of error reporting. pub expected_terminals: Vec, } impl NoMatch { - pub fn new(expected_terminals: Vec) -> Self { - Self { expected_terminals } + pub fn new(kind: Option, expected_terminals: Vec) -> Self { + Self { + kind, + expected_terminals, + } } } diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs index f54ae45934..c49819e30f 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/recovery.rs @@ -107,7 +107,7 @@ impl ParserResult { ParseResultKind::Incomplete => { ParserResult::incomplete_match(nodes, expected_terminals) } - ParseResultKind::NoMatch => ParserResult::no_match(expected_terminals), + ParseResultKind::NoMatch => ParserResult::no_match(None, expected_terminals), } } } diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs index a808e73796..a4fb3a2be5 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/repetition_helper.rs @@ -28,7 +28,10 @@ impl RepetitionHelper { // Couldn't get a full match but we allow 0 items - return an empty match // so the parse is considered valid but note the expected terminals - ParserResult::NoMatch(NoMatch { expected_terminals }) if MIN_COUNT == 0 => { + ParserResult::NoMatch(NoMatch { + kind: _, + expected_terminals, + }) if MIN_COUNT == 0 => { return ParserResult::r#match(vec![], expected_terminals); } // Don't try repeating if we don't have a full match and we require at least one @@ -62,7 +65,9 @@ impl RepetitionHelper { ParserResult::IncompleteMatch(IncompleteMatch { expected_terminals, .. }) - | ParserResult::NoMatch(NoMatch { expected_terminals }), + | ParserResult::NoMatch(NoMatch { + expected_terminals, .. + }), ) => { input.rewind(save); running.expected_terminals = expected_terminals; diff --git a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs index bcc5dc64f0..eca96115d0 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/parser/parser_support/separated_helper.rs @@ -91,7 +91,7 @@ impl SeparatedHelper { } ParserResult::NoMatch(no_match) => { return if accum.is_empty() { - ParserResult::no_match(no_match.expected_terminals) + ParserResult::no_match(None, no_match.expected_terminals) } else { ParserResult::incomplete_match(accum, no_match.expected_terminals) };