From 774959a8a61f08e24a6f16f0bb46fd46c7c86f22 Mon Sep 17 00:00:00 2001 From: Konstantin Vyatkin Date: Mon, 27 Jul 2026 11:17:09 +0200 Subject: [PATCH] fix(parser): notify listeners for fatal entry errors Dispatch committed ParserError values through registered error listeners at interpreted and generated public entry boundaries. Distinguish adaptive interpreted fallback errors from generated fatal errors so nested and speculative failures stay silent and recovery cannot notify twice. At a generated fatal boundary, roll back only partial tree state while retaining prior committed recovery diagnostics and syntax-error counts. Drain retained diagnostics before semantic-hook or parser-abort overrides and before reporting the final parser error, preventing stale diagnostics from leaking into a reused parser. Add direct-runtime and generated snapshot regressions, update the conformance harness to rely on listener output, and refresh the self-hosted parser artifacts. --- src/bin/antlr4-runtime-testsuite.rs | 2 +- src/bin/antlr4-rust-gen.rs | 97 +++++--- .../grammar/generated/antlr_v4_parser.rs | 153 ++++++------ src/parser.rs | 58 ++++- ...rpreted_parse_notifies_error_listener.snap | 27 +++ tests/antlr4_rust_gen_cli.rs | 229 ++++++++++++++++++ ..._preserves_prior_recovery_diagnostics.snap | 48 ++++ ...s_not_leak_prior_recovery_diagnostics.snap | 30 +++ .../antlr-v4-grammar/self-hosted.sha256 | 2 +- 9 files changed, 540 insertions(+), 106 deletions(-) create mode 100644 src/snapshots/antlr4_runtime__parser__tests__failed_interpreted_parse_notifies_error_listener.snap create mode 100644 tests/snapshots/antlr4_rust_gen_cli__fatal_entry_preserves_prior_recovery_diagnostics.snap create mode 100644 tests/snapshots/antlr4_rust_gen_cli__semantic_override_does_not_leak_prior_recovery_diagnostics.snap diff --git a/src/bin/antlr4-runtime-testsuite.rs b/src/bin/antlr4-runtime-testsuite.rs index a63ca324..4e5b3051 100644 --- a/src/bin/antlr4-runtime-testsuite.rs +++ b/src/bin/antlr4-runtime-testsuite.rs @@ -1071,7 +1071,7 @@ fn parser_smoke_main(descriptor: &Descriptor) -> String { "" }; format!( - "pub mod generated {{\n pub mod {lexer_module};\n pub mod {parser_module};\n}}\n\nuse antlr4_runtime::{{AntlrError, CommonTokenStream, InputStream, Parser}};\nuse generated::{lexer_module}::{lexer_type};\nuse generated::{parser_module}::{parser_type};\n\nfn main() {{\n let handle = std::thread::Builder::new()\n // Runtime-suite smoke crates run deeply nested generated parser paths;\n // this is harness-only and does not change the runtime's default stack.\n .stack_size(128 * 1024 * 1024)\n .spawn(|| {{\n let lexer = {lexer_type}::new(InputStream::new(\"{}\"));\n let tokens = CommonTokenStream::new(lexer);\n let mut parser = {parser_type}::new(tokens);\n parser.set_build_parse_trees({build_parse_trees});\n parser.set_report_diagnostic_errors({report_diagnostic_errors});\n{prediction_mode} if let Err(error) = parser.{start_rule}() {{\n match error {{\n AntlrError::ParserError {{ line, column, message, .. }} => eprintln!(\"line {{line}}:{{column}} {{message}}\"),\n other => eprintln!(\"{{other}}\"),\n }}\n }}\n }})\n .expect(\"parser smoke thread should start\");\n handle.join().expect(\"parser smoke thread should finish\");\n}}\n", + "pub mod generated {{\n pub mod {lexer_module};\n pub mod {parser_module};\n}}\n\nuse antlr4_runtime::{{AntlrError, CommonTokenStream, InputStream, Parser}};\nuse generated::{lexer_module}::{lexer_type};\nuse generated::{parser_module}::{parser_type};\n\nfn main() {{\n let handle = std::thread::Builder::new()\n // Runtime-suite smoke crates run deeply nested generated parser paths;\n // this is harness-only and does not change the runtime's default stack.\n .stack_size(128 * 1024 * 1024)\n .spawn(|| {{\n let lexer = {lexer_type}::new(InputStream::new(\"{}\"));\n let tokens = CommonTokenStream::new(lexer);\n let mut parser = {parser_type}::new(tokens);\n parser.set_build_parse_trees({build_parse_trees});\n parser.set_report_diagnostic_errors({report_diagnostic_errors});\n{prediction_mode} if let Err(error) = parser.{start_rule}() {{\n // Parser errors are emitted through the recognizer's listeners.\n // Keep surfacing non-syntax runtime failures so unsupported\n // behavior cannot disappear behind an ignored Result.\n if !matches!(&error, AntlrError::ParserError {{ .. }}) {{\n eprintln!(\"{{error}}\");\n }}\n }}\n }})\n .expect(\"parser smoke thread should start\");\n handle.join().expect(\"parser smoke thread should finish\");\n}}\n", rust_string(&descriptor.input) ) } diff --git a/src/bin/antlr4-rust-gen.rs b/src/bin/antlr4-rust-gen.rs index b9acb12d..c2493396 100644 --- a/src/bin/antlr4-rust-gen.rs +++ b/src/bin/antlr4-rust-gen.rs @@ -5580,6 +5580,25 @@ fn adaptive_atn_parser_render_slots(preferred_rule_count: usize) -> AdaptiveAtnP } } +fn render_generated_rule_error(retry_variant: &str, retry_into_error: &str) -> String { + format!( + r#"#[allow(dead_code)] +#[derive(Debug)] +enum GeneratedRuleError {{ + Fatal(antlr4_runtime::AntlrError), + Interpreted(antlr4_runtime::AntlrError), +{retry_variant}}} + +impl GeneratedRuleError {{ + fn into_error(self) -> antlr4_runtime::AntlrError {{ + match self {{ + Self::Fatal(error) | Self::Interpreted(error) => error, +{retry_into_error} }} + }} +}}"# + ) +} + #[allow(clippy::too_many_arguments)] fn render_generated_rule_routing( rules: &[Option], @@ -5809,7 +5828,7 @@ fn render_generated_rule_dispatch_with_rule_names( if let Some(invoking_state) = invoking_state {{\n \ self.base.push_invoking_state(invoking_state);\n \ }}\n \ - return self.parse_rule_precedence_from_generated({index}, precedence).map_err(GeneratedRuleError::Fatal);\n \ + return self.parse_rule_precedence_from_generated({index}, precedence).map_err(GeneratedRuleError::Interpreted);\n \ }}" ) .expect("writing to a string cannot fail"); @@ -6078,7 +6097,7 @@ fn render_generated_rule_method( .expect("writing to a string cannot fail"); writeln!( out, - " self.base.restore_generated_diagnostics(__generated_diagnostic_marker);" + " self.base.rollback_generated_tree(__generated_diagnostic_marker);" ) .expect("writing to a string cannot fail"); writeln!( @@ -6215,7 +6234,7 @@ fn render_generated_left_recursive_rule_method( .expect("writing to a string cannot fail"); writeln!( out, - " self.base.restore_generated_diagnostics(__generated_diagnostic_marker);" + " self.base.rollback_generated_tree(__generated_diagnostic_marker);" ) .expect("writing to a string cannot fail"); writeln!( @@ -10327,6 +10346,8 @@ fn render_parser_with_options( retry_variant: adaptive_atn_retry_variant, retry_into_error: adaptive_atn_retry_into_error, } = adaptive_atn_parser_render_slots(adaptive_atn_preferred_rule_count); + let generated_rule_error = + render_generated_rule_error(adaptive_atn_retry_variant, adaptive_atn_retry_into_error); let embedded_imports = if embedded_data.is_some() || structural_surface.is_some() { "#[allow(unused_imports)]\nuse std::io::Write as _;\n#[allow(unused_imports)]\nuse antlr4_runtime::{java_style_list, PredictionMode, BailErrorStrategy, TerminalNodeView as RuntimeTerminalNode, ErrorNodeView as RuntimeErrorNode, RuleNodeView, AsRuleNode, FromRuleNode, MissingChildError, Token as _};\n" @@ -10379,20 +10400,7 @@ where generated_only: bool, {adaptive_atn_preference_struct_field}{embedded_struct_fields}}} -#[allow(dead_code)] -#[derive(Debug)] -enum GeneratedRuleError {{ - Fatal(antlr4_runtime::AntlrError), -{adaptive_atn_retry_variant}}} - -impl GeneratedRuleError {{ - fn into_error(self) -> antlr4_runtime::AntlrError {{ - match self {{ - Self::Fatal(error) => error, -{adaptive_atn_retry_into_error} }} - }} -}} - +{generated_rule_error} impl {type_name} where L: TokenSource, @@ -10564,6 +10572,15 @@ where Ok(tree) => tree, Err(error) => {{ antlr4_runtime::IntStream::seek(self.base.input(), __rule_start); + let __report_error = + matches!(&error, GeneratedRuleError::Fatal(_)); + // A fatal unwind retains recovery diagnostics committed + // earlier in this entry. Dispatch them before a semantic + // or parser-abort override can return, or they would leak + // into the next entry on a reused parser. + if allow_generated_fallback && __report_error {{ + self.base.report_generated_parser_diagnostics(); + }} // A generated predicate that consulted an unimplemented hook // (returning None under the Error policy) fails the alternative // and surfaces here as a generic failed-predicate/rule error. @@ -10585,7 +10602,11 @@ where return Err(abort); }} }} - return Err(error.into_error()); + let error = error.into_error(); + if allow_generated_fallback && __report_error {{ + self.base.report_unrecovered_parser_error(&error); + }} + return Err(error); }} }} }} else if __generated_only {{ @@ -13282,7 +13303,7 @@ mod tests { let rendered = render_generated_rule_dispatch(&[Some(body)], &[], &BTreeMap::new(), false); assert!(rendered.contains("match_token_recovering(1, 2, atn())")); assert!(rendered.contains("generated_diagnostics_checkpoint()")); - assert!(rendered.contains("restore_generated_diagnostics(__generated_diagnostic_marker)")); + assert!(rendered.contains("rollback_generated_tree(__generated_diagnostic_marker)")); } #[test] @@ -13760,7 +13781,7 @@ mod tests { )); assert!(rendered.contains("return Err(GeneratedRuleError::AdaptiveRetry);")); assert!(rendered.contains( - "return self.parse_rule_precedence_from_generated(0, precedence).map_err(GeneratedRuleError::Fatal);" + "return self.parse_rule_precedence_from_generated(0, precedence).map_err(GeneratedRuleError::Interpreted);" )); assert!(rendered.contains("self.parse_generated_rule_1_adaptive_probe_dispatch(0, false)")); assert!(rendered.contains( @@ -14878,12 +14899,13 @@ mod tests { } #[test] - fn generated_parser_reports_lexer_errors_on_outer_success() { + fn generated_parser_reports_diagnostics_at_outer_boundaries() { let rendered = render_parser("TParser", &minimal_parser_data()).expect("parser should render"); assert!(rendered.contains("if allow_generated_fallback {")); assert!(rendered.contains("self.base.report_generated_parser_diagnostics();")); + assert!(rendered.contains("self.base.report_unrecovered_parser_error(&error);")); assert!(rendered.contains("fn number_of_syntax_errors(&self) -> usize")); assert!(!rendered.contains("self.base.report_token_source_errors();")); } @@ -14970,9 +14992,12 @@ mod tests { let count = rest .find("self.base.record_generated_syntax_error();") .expect("fatal sync path records syntax error"); + let rollback = rest + .find("self.base.rollback_generated_tree(__generated_diagnostic_marker);") + .expect("fatal sync path rolls back only partial tree state"); assert!( - guard < count && count < fatal, - "fatal sync path must increment before returning" + guard < rollback && rollback < count && count < fatal, + "fatal sync path must preserve diagnostics, roll back the tree, and increment before returning" ); // And the nested-child path recovers locally and returns Ok. let recover = rest @@ -17401,7 +17426,7 @@ dispose = "hook" } #[test] - fn generated_rule_error_prefers_recorded_semantic_error() { + fn generated_rule_error_drains_diagnostics_before_recorded_overrides() { // When a generated-direct predicate consulted an unimplemented hook // (returning None under the Error policy), the alternative fails and // `parse_generated_rule` returns a generic `failed_predicate_error`. The @@ -17424,8 +17449,12 @@ dispose = "hook" .expect("parser should render"); // Locate the generated-rule `Err` arm's generic return. - let generic_return_at = module - .find("return Err(error.into_error());") + let error_conversion_at = module + .find("let error = error.into_error();") + .expect("generated-rule Err arm converts the generic rule error"); + let generic_return_at = module[error_conversion_at..] + .find("return Err(error);") + .map(|offset| error_conversion_at + offset) .expect("generated-rule Err arm returns the generic rule error"); // The fail-loud drain must appear inside that arm, before the generic // return, under the top-level gate. @@ -17433,12 +17462,18 @@ dispose = "hook" .rfind("Err(error) => {") .expect("generic return lives in the Err arm"); let arm = &module[arm_start..generic_return_at]; + let diagnostics_at = arm + .find("self.base.report_generated_parser_diagnostics();") + .expect("the fatal Err arm drains retained diagnostics"); + let semantic_at = arm + .find("if let Some(semantic_error) = self.base.take_unknown_semantic_error()") + .expect("the Err arm drains a recorded semantic error"); + let abort_at = arm + .find("if let Some(abort) = self.base.take_parse_abort()") + .expect("the Err arm drains a recorded parser abort"); assert!( - arm.contains("if allow_generated_fallback {") - && arm.contains( - "if let Some(semantic_error) = self.base.take_unknown_semantic_error()" - ), - "the Err arm must drain a recorded semantic error before the generic return" + diagnostics_at < semantic_at && diagnostics_at < abort_at, + "retained diagnostics must be dispatched before either override can return" ); } diff --git a/src/bin_support/grammar/generated/antlr_v4_parser.rs b/src/bin_support/grammar/generated/antlr_v4_parser.rs index 0d44adc5..da2b21c2 100644 --- a/src/bin_support/grammar/generated/antlr_v4_parser.rs +++ b/src/bin_support/grammar/generated/antlr_v4_parser.rs @@ -10673,16 +10673,16 @@ where #[derive(Debug)] enum GeneratedRuleError { Fatal(antlr4_runtime::AntlrError), + Interpreted(antlr4_runtime::AntlrError), } impl GeneratedRuleError { fn into_error(self) -> antlr4_runtime::AntlrError { match self { - Self::Fatal(error) => error, + Self::Fatal(error) | Self::Interpreted(error) => error, } } } - impl AntlRv4Parser where L: TokenSource, @@ -10923,6 +10923,15 @@ where Ok(tree) => tree, Err(error) => { antlr4_runtime::IntStream::seek(self.base.input(), __rule_start); + let __report_error = + matches!(&error, GeneratedRuleError::Fatal(_)); + // A fatal unwind retains recovery diagnostics committed + // earlier in this entry. Dispatch them before a semantic + // or parser-abort override can return, or they would leak + // into the next entry on a reused parser. + if allow_generated_fallback && __report_error { + self.base.report_generated_parser_diagnostics(); + } // A generated predicate that consulted an unimplemented hook // (returning None under the Error policy) fails the alternative // and surfaces here as a generic failed-predicate/rule error. @@ -10944,7 +10953,11 @@ where return Err(abort); } } - return Err(error.into_error()); + let error = error.into_error(); + if allow_generated_fallback && __report_error { + self.base.report_unrecovered_parser_error(&error); + } + return Err(error); } } } else if __generated_only { @@ -11230,7 +11243,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11297,7 +11310,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11418,7 +11431,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11555,7 +11568,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11670,7 +11683,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11737,7 +11750,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -11917,7 +11930,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12037,7 +12050,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12151,7 +12164,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12258,7 +12271,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12365,7 +12378,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12524,7 +12537,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12641,7 +12654,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12758,7 +12771,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12815,7 +12828,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -12911,7 +12924,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13028,7 +13041,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13134,7 +13147,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13247,7 +13260,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13607,7 +13620,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13760,7 +13773,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13827,7 +13840,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -13889,7 +13902,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14002,7 +14015,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14064,7 +14077,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14181,7 +14194,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14243,7 +14256,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14310,7 +14323,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14421,7 +14434,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14478,7 +14491,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14537,7 +14550,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14651,7 +14664,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14760,7 +14773,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14920,7 +14933,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -14979,7 +14992,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15093,7 +15106,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15241,7 +15254,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15394,7 +15407,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15654,7 +15667,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15719,7 +15732,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15836,7 +15849,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -15953,7 +15966,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16064,7 +16077,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16175,7 +16188,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16289,7 +16302,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16489,7 +16502,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16803,7 +16816,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -16923,7 +16936,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17095,7 +17108,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17216,7 +17229,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17322,7 +17335,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17381,7 +17394,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17631,7 +17644,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17771,7 +17784,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -17900,7 +17913,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18004,7 +18017,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18116,7 +18129,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18236,7 +18249,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18451,7 +18464,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18660,7 +18673,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18811,7 +18824,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -18874,7 +18887,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -19077,7 +19090,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -19197,7 +19210,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -19369,7 +19382,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -19426,7 +19439,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } @@ -19540,7 +19553,7 @@ where if let Some(__error) = __sync_error { if allow_fallback { self.base.exit_rule(); - self.base.restore_generated_diagnostics(__generated_diagnostic_marker); + self.base.rollback_generated_tree(__generated_diagnostic_marker); self.base.record_generated_syntax_error(); return Err(GeneratedRuleError::Fatal(__error)); } diff --git a/src/parser.rs b/src/parser.rs index 65538e20..354b3049 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5196,6 +5196,11 @@ where self.syntax_errors = self.syntax_errors.saturating_add(count); } + /// Returns whether no interpreted rule context or generated invocation is active. + const fn is_top_level_entry(&self) -> bool { + self.rule_context_stack.is_empty() && self.pending_invoking_states.is_empty() + } + /// Emits diagnostics buffered by the token stream while generated parser /// code was fetching lexer tokens directly. pub fn report_token_source_errors(&mut self) { @@ -5218,6 +5223,15 @@ where self.generated_parser_diagnostics .truncate(marker.diagnostics_len); self.syntax_errors = marker.syntax_errors; + self.rollback_generated_tree(marker); + } + + /// Rolls back generated tree state while retaining committed diagnostics. + /// + /// Fatal public entries use this after an earlier child recovery: the + /// partial tree is discarded, but ANTLR has already committed the child's + /// diagnostic and syntax-error count. + pub fn rollback_generated_tree(&mut self, marker: GeneratedDiagnosticsCheckpoint) { self.generated_sync_expected = None; self.tree.rollback(marker.tree); } @@ -5229,6 +5243,24 @@ where self.dispatch_generated_diagnostics(&parser_diagnostics, &token_errors); } + /// Emits a fatal parser error after an entry-rule parse commits to returning it. + /// + /// Generated parsers call this only at their public entry boundary. Nested + /// failures remain silent until generated recovery commits and buffers them. + pub fn report_unrecovered_parser_error(&self, error: &AntlrError) { + let AntlrError::ParserError { + line, + column, + message, + offending, + } = error + else { + return; + }; + let offending = offending.and_then(|token| self.token_store().view(token)); + self.notify_error_listeners(offending, *line, *column, message, Some(error)); + } + fn dispatch_parser_diagnostic(&self, diagnostic: &ParserDiagnostic) { let offending = diagnostic .offending @@ -7210,6 +7242,7 @@ where predicate_context: Option>, alt_tracking: AltNumberTracking, ) -> Result { + let report_unrecovered_error = self.is_top_level_entry(); let start_state = atn.rule_to_start_state().get(rule_index).ok_or_else(|| { AntlrError::Unsupported(format!("rule {rule_index} has no start state")) })?; @@ -7295,6 +7328,9 @@ where let error = self.recognition_error(rule_index, start_index, &expected); self.record_syntax_errors(1); self.report_token_source_errors(); + if report_unrecovered_error { + self.report_unrecovered_parser_error(&error); + } error })? } else { @@ -7767,6 +7803,7 @@ where precedence: i32, options: ParserRuntimeOptions<'_>, ) -> Result<(ParseTree, Vec), AntlrError> { + let report_unrecovered_error = self.is_top_level_entry(); let ParserRuntimeOptions { init_action_rules, track_alt_numbers, @@ -7907,6 +7944,9 @@ where let error = self.recognition_error(rule_index, start_index, &expected); self.record_syntax_errors(1); self.report_token_source_errors(); + if report_unrecovered_error { + self.report_unrecovered_parser_error(&error); + } return Err(error); }; @@ -17246,19 +17286,31 @@ mod tests { } #[test] - fn parser_syntax_error_count_tracks_failed_interpreted_parse() { + fn failed_interpreted_parse_notifies_error_listener() { let atn = token_then_eof_atn(); let mut parser = mini_parser(vec![ - TestToken::new(2).with_text("y"), + TestToken::new(2) + .with_text("y") + .with_span(0, 0) + .with_position(3, 5), TestToken::eof("parser-test", 1, 1, 1), ]); + parser.remove_error_listeners(); + let diagnostics = Arc::new(Mutex::new(Vec::new())); + parser.add_error_listener(RecordingErrorListener { + diagnostics: Arc::clone(&diagnostics), + }); let error = parser .parse_atn_rule(&atn, 0) .expect_err("start-rule mismatch should remain a parser error"); assert_eq!(parser.number_of_syntax_errors(), 1); - assert!(matches!(error, AntlrError::ParserError { .. })); + assert!(matches!(&error, AntlrError::ParserError { .. })); + insta::assert_debug_snapshot!( + "failed_interpreted_parse_notifies_error_listener", + *diagnostics.lock().expect("recorded diagnostics lock") + ); } #[test] diff --git a/src/snapshots/antlr4_runtime__parser__tests__failed_interpreted_parse_notifies_error_listener.snap b/src/snapshots/antlr4_runtime__parser__tests__failed_interpreted_parse_notifies_error_listener.snap new file mode 100644 index 00000000..c3272c87 --- /dev/null +++ b/src/snapshots/antlr4_runtime__parser__tests__failed_interpreted_parse_notifies_error_listener.snap @@ -0,0 +1,27 @@ +--- +source: src/parser.rs +expression: "*diagnostics.lock().expect(\"recorded diagnostics lock\")" +--- +[ + RecordedDiagnostic { + grammar_file_name: "Mini.g4", + offending_text: Some( + "y", + ), + line: 3, + column: 5, + message: "mismatched input 'y' expecting 'x'", + error: Some( + ParserError { + line: 3, + column: 5, + message: "mismatched input 'y' expecting 'x'", + offending: Some( + TokenId( + 0, + ), + ), + }, + ), + }, +] diff --git a/tests/antlr4_rust_gen_cli.rs b/tests/antlr4_rust_gen_cli.rs index 29442e6a..1bec3596 100644 --- a/tests/antlr4_rust_gen_cli.rs +++ b/tests/antlr4_rust_gen_cli.rs @@ -307,6 +307,235 @@ fn adaptive_atn_routing_generated_path_compiles() { ); } +#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O. +#[test] +fn unrecovered_generated_entry_errors_notify_listeners_once() { + let temp = temporary_directory("fatal-error-listener"); + let grammar = temp.path().join("Fatal.g4"); + let out = temp.path().join("generated"); + fs::write( + &grammar, + "grammar Fatal;\nfatal: A (B B | C C);\nstart: child EOF;\nmixed: child A (B B | C C);\nsemantic_mixed: child semantic_child A (B B | C C);\nsemantic_child: {unsupported()}?;\nclean: A;\nchild: A (B B | C C);\nA: 'a';\nB: 'b';\nC: 'c';\nD: 'd';\n", + ) + .expect("grammar should be writable"); + + let output = run_antlr4_rust_gen(&[ + grammar.as_os_str(), + OsStr::new("--actions"), + OsStr::new("templates"), + OsStr::new("--sem-unknown"), + OsStr::new("hook"), + OsStr::new("--out-dir"), + out.as_os_str(), + ]); + assert!( + output.status.success(), + "stdout: {}\nstderr: {}", + utf8(&output.stdout), + utf8(&output.stderr) + ); + + let test_source = r####" +#[cfg(test)] +mod fatal_error_listener_tests { + use std::sync::{Arc, Mutex}; + + use super::fatal_lexer::FatalLexer; + use super::fatal_parser::FatalParser; + use antlr4_runtime::{ + AntlrError, CommonTokenStream, ErrorListener, InputStream, Parser as _, Recognizer, + TokenView, + }; + + #[derive(Clone, Debug, Eq, PartialEq)] + struct Event { + offending_text: Option, + line: usize, + column: usize, + message: String, + error: Option, + } + + #[allow(dead_code)] + #[derive(Debug)] + struct EntrySnapshot<'a> { + returned_error: &'a AntlrError, + syntax_errors: usize, + events: &'a [Event], + } + + #[derive(Clone, Debug)] + struct RecordingListener { + events: Arc>>, + } + + impl ErrorListener for RecordingListener + where + R: Recognizer + ?Sized, + { + fn syntax_error( + &mut self, + _recognizer: &R, + offending: Option>, + line: usize, + column: usize, + message: &str, + error: Option<&AntlrError>, + ) { + self.events.lock().expect("events lock").push(Event { + offending_text: offending.and_then(|token| token.text().map(str::to_owned)), + line, + column, + message: message.to_owned(), + error: error.cloned(), + }); + } + } + + fn parser( + input: &str, + ) -> ( + FatalParser>, + Arc>>, + ) { + let lexer = FatalLexer::new(InputStream::new(input)); + let mut parser = FatalParser::new(CommonTokenStream::new(lexer)); + // A configured (effectively unbounded) cap selects generated bodies + // for rules the normal performance routing prefers to interpret. + parser.set_max_rule_depth(Some(usize::MAX)); + parser.remove_error_listeners(); + let events = Arc::new(Mutex::new(Vec::new())); + parser.add_error_listener(RecordingListener { + events: Arc::clone(&events), + }); + (parser, events) + } + + #[test] + fn fatal_public_entry_reports_the_returned_error() { + let (mut parser, events) = parser("ad"); + + let error = parser + .fatal() + .expect_err("invalid first token should remain fatal"); + + assert_eq!(parser.number_of_syntax_errors(), 1); + let events = events.lock().expect("events lock"); + assert_eq!(events.len(), 1, "fatal error must be reported exactly once"); + let event = &events[0]; + assert_eq!(event.offending_text.as_deref(), Some("d")); + let AntlrError::ParserError { + line, + column, + message, + .. + } = &error + else { + panic!("expected a positioned parser error, got {error:?}"); + }; + assert_eq!((event.line, event.column), (*line, *column)); + assert_eq!(&event.message, message); + assert_eq!(event.error.as_ref(), Some(&error)); + } + + #[test] + fn recovered_nested_error_is_not_reported_twice() { + let (mut parser, events) = parser("ad"); + + parser + .start() + .expect("the parent should recover the nested child error"); + + assert_eq!(parser.number_of_syntax_errors(), 1); + let events = events.lock().expect("events lock"); + assert_eq!(events.len(), 1, "recovery must report the error exactly once"); + assert_eq!(events[0].offending_text.as_deref(), Some("d")); + } + + #[test] + fn fatal_entry_preserves_prior_recovery_diagnostics() { + let (mut parser, events) = parser("adad"); + + let error = parser + .mixed() + .expect_err("the entry should fail after the child recovery"); + + let events = events.lock().expect("events lock"); + let snapshot = EntrySnapshot { + returned_error: &error, + syntax_errors: parser.number_of_syntax_errors(), + events: &events, + }; + std::fs::write( + concat!(env!("CARGO_MANIFEST_DIR"), "/fatal-entry-events.txt"), + format!("{snapshot:#?}\n"), + ) + .expect("fatal entry snapshot should be writable"); + } + + #[test] + fn semantic_override_does_not_leak_prior_recovery_diagnostics() { + let (mut parser, events) = parser("adad"); + + let error = parser + .semantic_mixed() + .expect_err("the semantic override should win over the fatal parser error"); + assert!( + matches!(&error, AntlrError::Unsupported(_)), + "expected the configured fail-loud semantic error, got {error:?}" + ); + + let reported_before_reuse = events.lock().expect("events lock").len(); + parser + .clean() + .expect("the clean entry should succeed on the rewound input"); + let events = events.lock().expect("events lock"); + assert_eq!( + events.len(), + reported_before_reuse, + "the clean entry must not emit diagnostics retained by the failed entry" + ); + + let snapshot = EntrySnapshot { + returned_error: &error, + syntax_errors: parser.number_of_syntax_errors(), + events: &events, + }; + std::fs::write( + concat!( + env!("CARGO_MANIFEST_DIR"), + "/semantic-override-events.txt" + ), + format!("{snapshot:#?}\n"), + ) + .expect("semantic override snapshot should be writable"); + } +} +"####; + + assert_generated_project( + temp.path(), + &["fatal_lexer.rs", "fatal_parser.rs"], + test_source, + ); + let fatal_entry = + fs::read_to_string(temp.path().join("compile-generated/fatal-entry-events.txt")) + .expect("fatal entry snapshot should be emitted"); + insta::assert_snapshot!( + "fatal_entry_preserves_prior_recovery_diagnostics", + fatal_entry + ); + let semantic_override = fs::read_to_string( + temp.path() + .join("compile-generated/semantic-override-events.txt"), + ) + .expect("semantic override snapshot should be emitted"); + insta::assert_snapshot!( + "semantic_override_does_not_leak_prior_recovery_diagnostics", + semantic_override + ); +} + /// Editors on Windows commonly save `.g4` sources with a UTF-8 byte order mark /// and CRLF line endings. Both must generate exactly like the plain spelling. #[test] diff --git a/tests/snapshots/antlr4_rust_gen_cli__fatal_entry_preserves_prior_recovery_diagnostics.snap b/tests/snapshots/antlr4_rust_gen_cli__fatal_entry_preserves_prior_recovery_diagnostics.snap new file mode 100644 index 00000000..f216ef97 --- /dev/null +++ b/tests/snapshots/antlr4_rust_gen_cli__fatal_entry_preserves_prior_recovery_diagnostics.snap @@ -0,0 +1,48 @@ +--- +source: tests/antlr4_rust_gen_cli.rs +expression: fatal_entry +--- +EntrySnapshot { + returned_error: ParserError { + line: 1, + column: 3, + message: "mismatched input 'd' expecting {'b', 'c'}", + offending: Some( + TokenId( + 3, + ), + ), + }, + syntax_errors: 2, + events: [ + Event { + offending_text: Some( + "d", + ), + line: 1, + column: 1, + message: "mismatched input 'd' expecting {'b', 'c'}", + error: None, + }, + Event { + offending_text: Some( + "d", + ), + line: 1, + column: 3, + message: "mismatched input 'd' expecting {'b', 'c'}", + error: Some( + ParserError { + line: 1, + column: 3, + message: "mismatched input 'd' expecting {'b', 'c'}", + offending: Some( + TokenId( + 3, + ), + ), + }, + ), + }, + ], +} diff --git a/tests/snapshots/antlr4_rust_gen_cli__semantic_override_does_not_leak_prior_recovery_diagnostics.snap b/tests/snapshots/antlr4_rust_gen_cli__semantic_override_does_not_leak_prior_recovery_diagnostics.snap new file mode 100644 index 00000000..29e82d5a --- /dev/null +++ b/tests/snapshots/antlr4_rust_gen_cli__semantic_override_does_not_leak_prior_recovery_diagnostics.snap @@ -0,0 +1,30 @@ +--- +source: tests/antlr4_rust_gen_cli.rs +expression: semantic_override +--- +EntrySnapshot { + returned_error: Unsupported( + "unsupported semantic predicate: rule=semantic_child(4) pred_index=0", + ), + syntax_errors: 3, + events: [ + Event { + offending_text: Some( + "d", + ), + line: 1, + column: 1, + message: "mismatched input 'd' expecting {'b', 'c'}", + error: None, + }, + Event { + offending_text: Some( + "a", + ), + line: 1, + column: 2, + message: "rule failed predicate: semantic predicate", + error: None, + }, + ], +} diff --git a/third_party/antlr-v4-grammar/self-hosted.sha256 b/third_party/antlr-v4-grammar/self-hosted.sha256 index 50d2feef..de89c768 100644 --- a/third_party/antlr-v4-grammar/self-hosted.sha256 +++ b/third_party/antlr-v4-grammar/self-hosted.sha256 @@ -3,4 +3,4 @@ d1c01af37d665bd94f318c25265bbbf07620689effeb562b460cf75d361d2ee9 third_party/an c7114545a75ab294215819962e92e570383dc830fd5768463dab04e6733bcb80 third_party/antlr-v4-grammar/predefined.tokens 5803594bd2c8dd2d5180f1ca08fc70dfc80308479d18a7c4a1b743fa523b55ec third_party/antlr-v4-grammar/antlr-v4.toml d0450a71c4af4ebbb35c635b2e59592f1b1a4d289e6747e8c5f9133f6d15eaeb src/bin_support/grammar/generated/antlr_v4_lexer.rs -991b4073a8f4d84a1c70115939f0c323d9daf5e3742ead44fa6d2da01bbbfde0 src/bin_support/grammar/generated/antlr_v4_parser.rs +0024a734fede05ad73b8ea5ada1bbd98c47e13044e63983941793664b8c0bffd src/bin_support/grammar/generated/antlr_v4_parser.rs