Skip to content

feat(codegen): table-drive generated rule dispatch - #338

Merged
tinovyatkin merged 2 commits into
mainfrom
issue-322-dispatch-table
Aug 11, 2026
Merged

feat(codegen): table-drive generated rule dispatch#338
tinovyatkin merged 2 commits into
mainfrom
issue-322-dispatch-table

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace every per-rule parse_generated_rule_N_dispatch wrapper with a generated function-pointer table
  • apply depth-cap, parse-listener, stack-growth, and balanced-exit guards in one runtime-owned dispatcher
  • route ordinary entries and direct subrule calls through the table while preserving ATN-preferred and adaptive routing exceptions
  • make generated-code API revision 12 an intentional breaking boundary and regenerate every checked-in recognizer, including the XPath lexer

Compatibility

Revision 12 is now the only accepted generated-code API. The legacy dispatch/retry macro forms and frozen revision-9 compatibility fixtures are removed; older generated recognizers must be regenerated.

Measurements

Parser Lines Bytes
TOML 2,464 -> 2,329 (-135) 141,165 -> 131,673 (-9,492)
ANTLR v4 6,899 -> 6,506 (-393) 440,410 -> 412,613 (-27,797)
Rust 30,297 -> 29,040 (-1,257) 2,268,215 -> 2,176,081 (-92,134)
Kotlin parity 23,813 -> 23,012 (-801) 1,774,876 -> 1,716,627 (-58,249)

The release antlr4-rust-gen binary changed from 38,477,680 to 37,720,592 bytes: -757,088 bytes (-1.97%).

Seven-round Kotlin parse-only mean averages remained within noise:

Snippet Before After
nested types 0.111 ms 0.110 ms
dataframe 0.727 ms 0.724 ms
string templates 0.331 ms 0.332 ms

Validation

  • cargo test --locked --workspace --all-features
  • cargo clippy --locked --workspace --all-targets --all-features -- -D warnings
  • ANTLR runtime-testsuite: 357 passed, 0 failed
  • Kotlin parity: all Kotlin and script fixtures match the Python oracle
  • JavaScript parity: all token streams and parse trees match the Python oracle
  • TypeScript parity: all token streams and parse trees match the Java oracle
  • TOML and Rust checked-in recognizer regeneration checks
  • ANTLR v4 Stage 0 -> Stage 1 -> Stage 2 self-hosting fixed-point check

Fixes #322

Summary by CodeRabbit

  • Documentation

    • Updated compatibility guidance to define generated-code API revision 12 as the sole supported contract.
  • Breaking Changes

    • Older generated recognizers using revisions 1–11 are no longer compatible and must be regenerated.
    • Legacy generated parser dispatch interfaces have been removed.
  • Improvements

    • Generated parsers now use centralized runtime handling for rule dispatch, depth limits, listener lifecycle events, and fallback behavior.
    • Updated code-generation and integration coverage validates the revised parser behavior.

Replace one generated dispatch wrapper per parser rule with a function-pointer table and a single runtime-owned guard. Ordinary routing and direct subrule calls index the table while adaptive and interpreted exceptions retain explicit branches, preserving depth caps, listeners, stack growth, balanced exits, and left-recursive precedence.

Make generated-code API revision 12 an intentional breaking boundary, remove the legacy dispatch and retry compatibility paths plus the revision-9 fixtures, and regenerate every checked-in recognizer including the XPath lexer.

This removes 1,785 generated lines and 129,423 source bytes from the three checked-in parsers. The multi-parser generator binary shrinks by 757,088 bytes without measurable Kotlin parse regression.

Fixes #322
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Copy/Paste Detection

Found 36 duplication(s) across 9 changed non-generated Rust file(s) (threshold: 100 tokens).

Show duplications

Found a 21 line (226 tokens) duplication in the following files:

  • Starting at line 15287 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 16692 of crates/antlr-rust-runtime/src/parser.rs
            (9, AtnStateKind::RuleStop),
        ] {
            assert_eq!(atn.add_state(kind, Some(0)).expect("state").index(), state);
        }
        atn.set_left_recursive_rule(0)
            .expect("left-recursive rule start");
        atn.set_precedence_rule_decision(2)
            .expect("precedence decision");
        atn.set_loop_back_state(8, 7).expect("loop-back state");
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![9])
            .expect("rule stop states");
        for state in [1, 2, 3] {
            atn.add_decision_state(state).expect("decision state");
        }
        for (source, target) in [(0, 1), (2, 3), (2, 8), (7, 2), (8, 9)] {
            atn.add_transition(source, ParserTransitionSpec::Epsilon { target })
                .expect("epsilon transition");
        }
        for (source, target, label) in [(1, 2, 1), (1, 2, 2), (4, 6, 4), (5, 6, 3), (6, 7, 1)] {
```rust

---

Found a 44 line (215 tokens) duplication in the following files:
* Starting at line 4277 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 17033 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn plus_loop_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusLoopBack, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::LoopEnd, Some(0))
            .expect("state")
            .index(),
        5
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        6
    );

Found a 25 line (193 tokens) duplication in the following files:

  • Starting at line 16621 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17588 of crates/antlr-rust-runtime/src/parser.rs
        let mut atn = ParserAtnBuilder::new(1);
        for (state_number, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::StarLoopEntry),
            (2, AtnStateKind::Basic),
            (3, AtnStateKind::Basic),
            (4, AtnStateKind::StarLoopBack),
            (5, AtnStateKind::LoopEnd),
            (6, AtnStateKind::RuleStop),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(0)).expect("state").index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![6])
            .expect("rule stop states");
        atn.add_decision_state(1).expect("decision state");
        atn.set_loop_back_state(5, 4).expect("loop back state");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("entry transition");
        atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
            .expect("loop body");
```rust

---

Found a 39 line (188 tokens) duplication in the following files:
* Starting at line 4141 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 16741 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn block_decision_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        5
    );
    atn.set_end_state(1, 4).expect("block end state");

Found a 27 line (145 tokens) duplication in the following files:

  • Starting at line 18109 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 18243 of crates/antlr-rust-runtime/src/parser.rs
    fn generated_match_token_recovers_missing_token_from_context_follow() {
        let atn = generated_match_recovery_atn();
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new(
                [None, Some("'X'"), Some("'Y'")],
                [None, Some("X"), Some("Y")],
                [None::<&str>, None, None],
            ),
        );
        let mut parser = BaseParser::new(
            CommonTokenStream::new(Source {
                tokens: vec![TestToken::eof("parser-test", 3, 1, 3)],
                index: 0,
            }),
            data,
        );
        parser.rule_context_stack = vec![
            RuleContextFrame {
                rule_index: 0,
                invoking_state: 0,
            },
            RuleContextFrame {
                rule_index: 1,
                invoking_state: 1,
            },
        ];
```rust

---

Found a 26 line (142 tokens) duplication in the following files:
* Starting at line 4179 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4402 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
    atn.set_end_state(1, 4).expect("block end state");
    atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 3 })
        .expect("transition");
    atn.add_transition(
        2,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 1,
        },
    )
    .expect("transition");
    atn.add_transition(
        3,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 2,
        },
    )
    .expect("transition");
    atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 5 })
        .expect("transition");
    atn.add_decision_state(1).expect("decision state");

Found a 26 line (128 tokens) duplication in the following files:

  • Starting at line 4090 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 17705 of crates/antlr-rust-runtime/src/parser.rs
fn linear_rule_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
            .expect("state")
            .index(),
        3
    );
```rust

---

Found a 18 line (128 tokens) duplication in the following files:
* Starting at line 16287 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16312 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn epsilon_cycle_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(1);
        for (state_number, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::Basic),
            (2, AtnStateKind::RuleStop),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(0)).expect("state").index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![2])
            .expect("rule stop states");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("transition");

Found a 27 line (127 tokens) duplication in the following files:

  • Starting at line 4142 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16814 of crates/antlr-rust-runtime/src/parser.rs
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
```rust

---

Found a 27 line (127 tokens) duplication in the following files:
* Starting at line 16742 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16814 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::BlockStart, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            3
        );
        assert_eq!(
            atn.add_state(AtnStateKind::BlockEnd, Some(0))

Found a 26 line (125 tokens) duplication in the following files:

  • Starting at line 792 of crates/antlr-rust-runtime/src/generated.rs
  • Starting at line 963 of crates/antlr-rust-runtime/src/generated.rs
            $input: $crate::char_stream::CharStream,
            $hooks: $crate::parser::SemanticHooks,
        {
            pub fn metadata() -> &'static $crate::generated::GrammarMetadata {
                $metadata()
            }

            /// Adds a listener for lexer diagnostics.
            pub fn add_error_listener<T>(&mut self, listener: T)
            where
                T: for<'a> $crate::errors::ErrorListener<dyn $crate::recognizer::Recognizer + 'a>
                    + ::core::marker::Send
                    + 'static,
            {
                $crate::recognizer::Recognizer::add_error_listener(&mut self.$base, listener);
            }

            /// Removes every lexer error listener, including the default console listener.
            pub fn remove_error_listeners(&mut self) {
                $crate::recognizer::Recognizer::remove_error_listeners(&mut self.$base);
            }

            /// Routes every token through ATN interpretation instead of the compiled
            /// lexer DFA, so the learned-DFA trace (`lexer_dfa_string`) observes each
            /// match.
            pub fn set_force_interpreted(&mut self, force_interpreted: bool) {
```rust

---

Found a 22 line (125 tokens) duplication in the following files:
* Starting at line 16768 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16840 of crates/antlr-rust-runtime/src/parser.rs

```rust
            atn.add_state(AtnStateKind::BlockEnd, Some(0))
                .expect("state")
                .index(),
            4
        );
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStop, Some(0))
                .expect("state")
                .index(),
            5
        );
        atn.set_rule_to_start_state(vec![0])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![5])
            .expect("rule stop states");
        atn.add_decision_state(1).expect("decision state");
        atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
            .expect("transition");
        atn.add_transition(
            1,
            ParserTransitionSpec::Atom {
                target: 2,

Found a 34 line (119 tokens) duplication in the following files:

  • Starting at line 10872 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 10947 of crates/antlr-rust-runtime/src/parser.rs
                        outcomes.extend(
                            self.recognize_state(
                                atn,
                                RecognizeRequest {
                                    state_number: *target,
                                    stop_state,
                                    index,
                                    rule_start_index,
                                    decision_start_index: next_decision_start_index,
                                    init_action_rules,
                                    predicates,
                                    semantics,
                                    rule_args,
                                    member_actions,
                                    return_actions,
                                    local_int_arg,
                                    member_values: member_values.clone(),
                                    return_values: return_values.clone(),
                                    rule_alt_number: next_alt_number,
                                    track_alt_numbers,
                                    consumed_eof,
                                    committed_decision: transition_committed,
                                    precedence,
                                    depth: depth + 1,
                                    recovery_symbols: epsilon_recovery_symbols.clone(),
                                    recovery_state: epsilon_recovery_state,
                                },
                                visiting,
                                memo,
                                expected,
                            )
                            .into_iter()
                            .map(|mut outcome| {
                                prepend_decision(&mut outcome, decision);
```rust

---

Found a 13 line (117 tokens) duplication in the following files:
* Starting at line 143 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 170 of crates/antlr-rust-runtime/src/parser.rs

```rust
        ordinary $parser:ident, $state:expr, $rule:expr, $allow_fallback:expr,
        $atn:expr, $fatal:path;
        retry [$($retry:tt)*];
        bind ($ctx:ident, $rule_start:ident, $consumed_eof:ident, $sync_error:ident);
        setup { $($setup:tt)* }
        body { $($body:tt)* }
        success { $($success:tt)* }
        recovery { $($recovery:tt)* }
    ) => {
        $crate::__antlr4_rust_generated_rule! {
            @body
            parser $parser;
            enter $parser.base.enter_rule($state, $rule);

Found a 25 line (115 tokens) duplication in the following files:

  • Starting at line 4150 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4360 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 16750 of crates/antlr-rust-runtime/src/parser.rs
        atn.add_state(AtnStateKind::BlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        3
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
            .expect("state")
            .index(),
        4
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
```rust

---

Found a 15 line (113 tokens) duplication in the following files:
* Starting at line 18168 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 18446 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn generated_match_token_counts_single_token_deletion_recovery() {
        let atn = generated_match_recovery_atn();
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new(
                [None, Some("'X'"), Some("'Y'"), Some("'Z'")],
                [None, Some("X"), Some("Y"), Some("Z")],
                [None::<&str>, None, None, None],
            ),
        );
        let mut parser = BaseParser::new(
            CommonTokenStream::new(Source {
                tokens: vec![
                    TestToken::new(3).with_text("z"),
                    TestToken::new(2).with_text("y"),

Found a 22 line (112 tokens) duplication in the following files:

  • Starting at line 4277 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4351 of crates/antlr-rust-codegen/src/generator/tests.rs
fn plus_loop_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::BlockEnd, Some(0))
```rust

---

Found a 18 line (112 tokens) duplication in the following files:
* Starting at line 15097 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17514 of crates/antlr-rust-runtime/src/parser.rs

```rust
            (4, AtnStateKind::Basic, 0),
            (5, AtnStateKind::RuleStop, 0),
            (6, AtnStateKind::RuleStart, 1),
            (7, AtnStateKind::Basic, 1),
            (8, AtnStateKind::RuleStop, 1),
        ] {
            assert_eq!(
                atn.add_state(kind, Some(rule_index))
                    .expect("state")
                    .index(),
                state_number
            );
        }
        atn.set_rule_to_start_state(vec![0, 6])
            .expect("rule start states");
        atn.set_rule_to_stop_state(vec![5, 8])
            .expect("rule stop states");
        atn.add_decision_state(2).expect("decision state");

Found a 12 line (112 tokens) duplication in the following files:

  • Starting at line 15340 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 15423 of crates/antlr-rust-runtime/src/parser.rs
        let mut atn = ParserAtnBuilder::new(1);
        for (state, kind, rule) in [
            (0, AtnStateKind::RuleStart, 0),
            (1, AtnStateKind::StarLoopEntry, 0),
            (2, AtnStateKind::Basic, 0), // ops hub
            (3, AtnStateKind::Basic, 0), // shift prec
            (4, AtnStateKind::Basic, 0), // shift first >
            (5, AtnStateKind::Basic, 0), // shift second >
            (6, AtnStateKind::Basic, 0), // rel prec
            (7, AtnStateKind::Basic, 0), // rel >
            (8, AtnStateKind::LoopEnd, 0),
            (9, AtnStateKind::RuleStop, 0),
```rust

---

Found a 22 line (112 tokens) duplication in the following files:
* Starting at line 17288 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17705 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn predicate_after_token_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))

Found a 22 line (111 tokens) duplication in the following files:

  • Starting at line 15162 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17288 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17705 of crates/antlr-rust-runtime/src/parser.rs
    fn left_recursive_loop_with_caller_follow_atn(caller_symbol: i32) -> Atn {
        let mut atn = ParserAtnBuilder::new(2);
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(0))
                .expect("state")
                .index(),
            0
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            1
        );
        assert_eq!(
            atn.add_state(AtnStateKind::Basic, Some(0))
                .expect("state")
                .index(),
            2
        );
        assert_eq!(
            atn.add_state(AtnStateKind::RuleStart, Some(1))
```rust

---

Found a 27 line (110 tokens) duplication in the following files:
* Starting at line 3504 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 3616 of crates/antlr-rust-codegen/src/generator/tests.rs

```rust
            decision: 0,
            alts: (1, 2),
            track_alt_number: false,
            allow_semantic_context: true,
            force_context: false,
            plus_loop: false,
            fast_path: None,
            body: &body,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    // The whole rendered star-loop captures the leading-predicate-to-exit-alt filtering.
    insta::assert_snapshot!(

Found a 14 line (110 tokens) duplication in the following files:

  • Starting at line 17838 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21783 of crates/antlr-rust-runtime/src/parser.rs
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                TestToken::new(1).with_text("x"),
                TestToken::eof("parser-test", 1, 1, 1),
            ],
            index: 0,
        };
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new([None, Some("'x'")], [None, Some("X")], [None::<&str>, None]),
        );
        let mut parser = BaseParser::new(CommonTokenStream::new(source), data);
        let matched = parser.match_token(1).expect("token 1 should match");
```rust

---

Found a 13 line (109 tokens) duplication in the following files:
* Starting at line 17838 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 21808 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn parser_matches_token_and_reports_mismatch() {
        let source = Source {
            tokens: vec![
                TestToken::new(1).with_text("x"),
                TestToken::eof("parser-test", 1, 1, 1),
            ],
            index: 0,
        };
        let data = RecognizerData::new(
            "Mini.g4",
            Vocabulary::new([None, Some("'x'")], [None, Some("X")], [None::<&str>, None]),
        );
        let mut parser = BaseParser::new(CommonTokenStream::new(source), data);

Found a 22 line (108 tokens) duplication in the following files:

  • Starting at line 4090 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 15162 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17288 of crates/antlr-rust-runtime/src/parser.rs
fn linear_rule_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStop, Some(0))
```rust

---

Found a 22 line (108 tokens) duplication in the following files:
* Starting at line 4351 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 17033 of crates/antlr-rust-runtime/src/parser.rs

```rust
fn plus_block_decision_atn() -> ParserAtn {
    let mut atn = ParserAtnBuilder::new(2);
    assert_eq!(
        atn.add_state(AtnStateKind::RuleStart, Some(0))
            .expect("state")
            .index(),
        0
    );
    assert_eq!(
        atn.add_state(AtnStateKind::PlusBlockStart, Some(0))
            .expect("state")
            .index(),
        1
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))
            .expect("state")
            .index(),
        2
    );
    assert_eq!(
        atn.add_state(AtnStateKind::Basic, Some(0))

Found a 22 line (108 tokens) duplication in the following files:

  • Starting at line 8662 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 9053 of crates/antlr-rust-runtime/src/parser.rs
    ) -> Option<RecognizeOutcome> {
        let (error_index, message) = self.expected_error_message(rule_index, start_index, expected);
        let diagnostic = diagnostic_for_token(self.token_at(error_index), message);
        let mut next_index = error_index;
        loop {
            let symbol = self.token_type_at(next_index);
            if sync_symbols.contains(&symbol) {
                if next_index == error_index {
                    return None;
                }
                break;
            }
            if symbol == TOKEN_EOF {
                break;
            }
            let after = self.consume_index(next_index, symbol);
            if after == next_index {
                break;
            }
            next_index = after;
        }
        let mut nodes = NodeSeqId::EMPTY;
```rust

---

Found a 15 line (108 tokens) duplication in the following files:
* Starting at line 22079 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 22103 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn outcome_ties_keep_later_non_recursive_alternative() {
        let arena = RecognitionArena::default();
        let first = RecognizeOutcome {
            index: 1,
            consumed_eof: false,
            alt_number: 0,
            member_values: MemberEnv::new(),
            return_values: BTreeMap::new(),
            diagnostics: DiagnosticSeqId::EMPTY,
            decisions: Vec::new(),
            actions: vec![ParserAction::new(1, 0, 0, None)],
            nodes: NodeSeqId::EMPTY,
        };
        let second = RecognizeOutcome {
            actions: vec![ParserAction::new(2, 0, 0, None)],

Found a 17 line (107 tokens) duplication in the following files:

  • Starting at line 7721 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 8416 of crates/antlr-rust-runtime/src/parser.rs
        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"))
        })?;
        let stop_state = atn
            .rule_to_stop_state()
            .get(rule_index)
            .filter(|state| *state != usize::MAX)
            .ok_or_else(|| {
                AntlrError::Unsupported(format!("rule {rule_index} has no stop state"))
            })?;

        let start_index = self.current_visible_index();
        self.clear_prediction_diagnostics();
        self.reset_per_parse_caches();
        self.reset_recognition_arena();
        let caller_follow_state = self.pending_invoking_follow_state(atn);
```rust

---

Found a 17 line (105 tokens) duplication in the following files:
* Starting at line 181 of crates/antlr-rust-runtime/src/generated.rs
* Starting at line 278 of crates/antlr-rust-runtime/src/generated.rs

```rust
            fn __from_node_with_invocation_states(
                node: $crate::RuleNodeView<'a>,
                invocation_states: Option<Vec<isize>>,
            ) -> Self {
                $(
                    let __default = <$attrs>::default();
                    let __attrs = node.generated_attrs::<$attrs>().unwrap_or(&__default);
                )?
                Self {
                    __node: __GeneratedRuleContext::Stored(node),
                    __invocation_states: invocation_states,
                    __state: std::marker::PhantomData,
                    $(
                        $($field: __attrs.$field.clone(),)+
                    )?
                }
            }

Found a 25 line (104 tokens) duplication in the following files:

  • Starting at line 3244 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3411 of crates/antlr-rust-codegen/src/generator/tests.rs
            state: 1,
            decision: 0,
            track_alt_number: false,
            allow_semantic_context: false,
            force_context: false,
            fast_path: None,
            alts: &alts,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    assert!(rendered.contains("ll1_decision_prediction(atn(), 1)"));
```rust

---

Found a 13 line (104 tokens) duplication in the following files:
* Starting at line 15275 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16679 of crates/antlr-rust-runtime/src/parser.rs

```rust
    fn labeled_left_recursive_operator_atn() -> Atn {
        let mut atn = ParserAtnBuilder::new(4);
        for (state, kind) in [
            (0, AtnStateKind::RuleStart),
            (1, AtnStateKind::BlockStart),
            (2, AtnStateKind::StarLoopEntry),
            (3, AtnStateKind::StarBlockStart),
            (4, AtnStateKind::Basic),
            (5, AtnStateKind::Basic),
            (6, AtnStateKind::Basic),
            (7, AtnStateKind::StarLoopBack),
            (8, AtnStateKind::LoopEnd),
            (9, AtnStateKind::RuleStop),

Found a 28 line (102 tokens) duplication in the following files:

  • Starting at line 3297 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 3457 of crates/antlr-rust-codegen/src/generator/tests.rs
            state: 1,
            decision: 0,
            track_alt_number: false,
            allow_semantic_context: true,
            force_context: false,
            fast_path: None,
            alts: &alts,
        },
        0,
        GeneratedStepRenderContext {
            current_rule_index: 0,
            embedded: None,
            portable_locals: None,
            decision_routing: DecisionRoutingRender::default(),
            inline_action_statements: &BTreeMap::new(),
            track_alt_numbers: false,
            track_context_alt_numbers: false,
            direct_generated_rule_calls: &[],
            atn_preferred_rule_calls: &[],
            adaptive_atn_preferred_rule_slots: &[],
            adaptive_atn_probe_rule_slots: &[],
        },
    );

    // One decision renders into a fresh String; snapshot the whole emitted control flow (the
    // semantic-context gate, both predicate probes, the alt rewrite, the no-viable fallback)
    // instead of six positive probes plus one negative guard.
    insta::assert_snapshot!(
```rust

---

Found a 17 line (102 tokens) duplication in the following files:
* Starting at line 781 of crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
* Starting at line 848 of crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs

```rust
        dir.join("L.g4").as_os_str(),
        OsStr::new("--sem-patterns"),
        dir.join("patterns.toml").as_os_str(),
        OsStr::new("--sem-unknown"),
        OsStr::new("error"),
        OsStr::new("--require-full-semantics"),
        OsStr::new("--out-dir"),
        out.as_os_str(),
    ]);
    assert!(
        output.status.success(),
        "stdout: {}\nstderr: {}",
        utf8(&output.stdout),
        utf8(&output.stderr)
    );

    let lexer = fs::read_to_string(out.join("l.rs")).expect("lexer should be emitted");

Found a 16 line (101 tokens) duplication in the following files:

  • Starting at line 4250 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4402 of crates/antlr-rust-codegen/src/generator/tests.rs
    atn.set_loop_back_state(3, 4).expect("loop back state");
    atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 3 })
        .expect("transition");
    atn.add_transition(
        2,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 1,
        },
    )
    .expect("transition");
    atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 1 })
```rust

---

Found a 13 line (100 tokens) duplication in the following files:
* Starting at line 7372 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 7396 of crates/antlr-rust-runtime/src/parser.rs

```rust
        let mut expected = BTreeSet::new();
        for index in (1..self.rule_context_stack.len()).rev() {
            let invoking_state = self.rule_context_stack[index].invoking_state;
            let Ok(state_number) = usize::try_from(invoking_state) else {
                continue;
            };
            let Some(Transition::Rule { follow_state, .. }) = atn
                .state(state_number)
                .and_then(|state| state.transitions().first())
                .map(ParserTransition::data)
            else {
                continue;
            };

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tinovyatkin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e7711a02-dc2e-4027-9716-8a6c4718a4a4

📥 Commits

Reviewing files that changed from the base of the PR and between d247f9d and 1be9ffe.

📒 Files selected for processing (2)
  • README.md
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
📝 Walkthrough

Walkthrough

The PR centralizes generated-rule execution through a runtime dispatcher and generated function-pointer table. It removes per-rule dispatch wrappers, updates routing and integration tests, advances the compatibility contract to revision 12, and removes revision-9 compatibility fixtures.

Changes

Generated-rule dispatch revision

Layer / File(s) Summary
Runtime dispatch contract
crates/antlr-rust-runtime/src/generated.rs, crates/antlr-rust-runtime/src/parser.rs
The runtime adds GeneratedRuleParser and dispatch_generated_rule. Depth, listener, stack-growth, and listener-exit handling move into the shared dispatcher. Obsolete dispatch and retry macro forms are removed.
Generated dispatch-table routing
crates/antlr-rust-codegen/src/parser/routing.rs, crates/antlr-rust-codegen/src/parser/render/rules.rs
Generated code emits a rule-body function-pointer table. Direct, adaptive, probe, fallback, and child calls use the shared dispatcher. Per-rule dispatch wrappers are no longer generated.
Revision 12 compatibility contract
crates/antlr-rust-runtime/src/lib.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs, README.md, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/fixtures/revision9/*
The runtime accepts only API revision 12. Compatibility diagnostics, documentation, and CLI tests reflect this contract. Revision-9 generated lexer and parser fixtures are removed.
Generator and integration validation
crates/antlr-rust-codegen/src/generator/tests.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/semantics.rs, third_party/antlr-v4-grammar/self-hosted.sha256
Assertions now verify dispatch-table entries and shared dispatcher calls across routing cases, deep nesting, semantics, actions, predicates, and embedded rules. Generated-source checksums are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedParser
  participant dispatch_generated_rule
  participant BaseParser
  GeneratedParser->>dispatch_generated_rule: pass rule index, precedence, fallback, and rule body
  dispatch_generated_rule->>BaseParser: check depth cap and listener entry
  dispatch_generated_rule->>GeneratedParser: invoke selected generated rule body
  dispatch_generated_rule->>BaseParser: report listener exit
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The implementation addresses table-driven dispatch and compatibility updates, but excluded snapshots and generated recognizers prevent full verification. Review the excluded snapshot files and generated recognizers to verify compatibility updates, regeneration, and the complete acceptance criteria.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: replacing per-rule dispatch wrappers with table-driven generated rule dispatch.
Out of Scope Changes check ✅ Passed The documented changes support issue #322, including dispatch refactoring, API revision 12, compatibility cleanup, tests, and generated artifact updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-322-dispatch-table

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @tinovyatkin's task in 20m 49s —— View job


Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Reviewed at 1be9ffe5 against origin/main. Four independent passes (2× CLAUDE.md compliance, 2× bug hunt) plus my own verification of the crux items.


What I verified

The dispatcher is a faithful transcription of the deleted macro arm. generated.rs:1805-1836 matches the removed dispatch arm of __antlr4_rust_generated_rule! statement for statement: depth-cap → listener-enter → stack-gated body → unconditional listener-exit → return. Critically, parse_listener_exit_rule still fires on the Err path (the result is bound, not early-returned), and exit is still skipped only when enter itself aborted — so the enter/exit balance contract is unchanged, not merely preserved by accident.

.expect("generated rule dispatch target") is unreachable in emitted code. All five emission sites were traced:

  • routing.rs:170/186/230/274/292 pass literal indices drawn from rules.iter().flatten(), so those slots are Some by construction.
  • The wildcard arm at routing.rs:199 passes the dynamic rule_index, but is gated by let _body = …get(rule_index).copied().flatten()?; at routing.rs:153.
  • The subrule call site at render/rules.rs:316 is gated on direct_generated_rule_calls[rule_index], defined as exactly rules[rule_index].is_some() (routing.rs:52).

The router rewrite is behavior-preserving. The old _ => None catch-all covered four cases; the new code covers out-of-range and body-less via the ?, and ATN-preferred guard-miss / adaptive preferred_rules[slot] == true via the newly added {index} => None, arms. Adding those explicit None arms is exactly what the new dispatching wildcard required — easy to get wrong, done right.

The positional table can't misalign. __GENERATED_RULE_BODIES is now built by position in rules while the target name comes from rule.rule_index — a new coupling the old {index} => … form didn't have. rules[i].rule_index == i is enforced at construction (ir/mod.rs:418-426) and validated (ir/optimize.rs:22-32). Mechanically confirmed on the regenerated output: 24/24, 67/67, and 215/215 table entries point at the same target the old per-rule wrapper did, including the four left-recursive _precedence entries in rust_parser.rs. Left-recursive rules correctly select the 3-arg _precedence shim — the 2-arg parse_generated_rule_{i} shim would not unify with GeneratedRuleBody<P>.

Revision-12 boundary is complete and self-consistent — the CLAUDE.md checklist for __ANTLR4_RUST_CODEGEN_API is satisfied item by item: constant bumped, accepted arms collapsed, diagnostic reworded, generated_modules_enforce_codegen_api_compatibility + both snapshots updated, docs updated, and all 7 checked-in recognizers regenerated (including xpath/generated/x_path_lexer.rs). Dropping arms 1–11 is required rather than optional here: the runtime no longer provides the dispatch/legacy-@retry macro surface those revisions emit, which is precisely CLAUDE.md's "retain an older accepted arm only while the runtime still provides every API surface that revision's generated source needs."

third_party/antlr-v4-grammar/self-hosted.sha256 recomputed — all six hashes match. tools/toml-syntax/update-generated.sh --check reports the checked-in TOML recognizer is current, so the src/generated/ no-hand-edit rule holds.

Build + test evidence (run locally on this branch)

Check Result
cargo check --locked --workspace --all-features --all-targets exit 0
cargo clippy --locked --all-targets --all-features -p antlr-rust-codegen -p antlr-rust-runtime -- -D warnings exit 0
cargo test -p antlr-rust-codegen --lib --all-features 896 passed, 0 failed
cargo test -p antlr-rust-runtime --all-features 388 passed, 0 failed
deeply_nested_input_parses_without_native_stack_overflow passed

The wildcard-only match rule_index { _ => … } in generated output would trip clippy::match_single_binding under -D warnings, but generated modules carry #[allow(warnings, …, clippy::all, clippy::pedantic, clippy::nursery)] — confirmed clean above, not just inferred.

On the three CodeRabbit comments

All three are already resolved on this head:

  1. Missing compact_left_recursive_rule_lifecycle snapshot — false positive. The file exists and was updated in d247f9d9 (+16/-2); CodeRabbit's config excludes **/*.snap, so it couldn't see it.
  2. Strengthen the legacy-wrapper assertion — done in 1be9ffe5. I simulated the check against real output: it matches fn parse_generated_rule_0_dispatch(, and correctly does not match _adaptive_dispatch, _adaptive_probe_dispatch, _precedence, or plain parse_generated_rule_0(&mut self, __precedence… (that one splits at the first _ of __precedence, leaving a non-numeric index). No false positives or negatives.
  3. README should name both exceptions — already does: README.md:176-178 reads "generated code retains explicit ATN-preference and adaptive-routing exceptions."

Non-blocking observations (no action needed to merge)

  • The dispatcher is #[inline(never)], so there is now one mandatory native frame per rule level where the old per-rule wrapper could be inlined away. generated_rule_stack_check_due() samples on rule_context_stack.len() % 8 rather than a call counter, so the sampling cadence is unchanged and the 10,000-level test still clears the 1 MiB red zone. This is the thing most likely to have silently regressed; it didn't.
  • cli.rs:121 hardcodes "supports revision 12" while cli.rs:96-99 derives the current revision from the constant, so the next bump needs a manual edit there. The snapshot catches it, so it's a papercut rather than a correctness risk.
  • In generated_parent_keeps_interpreted_child_call, assert!(rendered.contains("None,")) is a weak stand-in for "rule 1 has a None table slot" — it doesn't pin the position. The adjacent !rendered.contains("parse_generated_rule_1(") still carries the real weight.
    · branch issue-322-dispatch-table

@codspeed-hq

codspeed-hq Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing issue-322-dispatch-table (1be9ffe) with main (65cdc82)

Open in CodSpeed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/antlr-rust-codegen/src/generator/tests.rs`:
- Around line 41-45: Add and accept the regenerated named snapshot for
compact_left_recursive_rule_lifecycle under the generator snapshots directory,
ensuring it matches the current rendered output used by
rendered_generated_rule_methods and is included in the repository.

In `@crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs`:
- Around line 157-165: Strengthen the generated-parser assertion in the test
around the emitted parser content so it rejects every legacy
parse_generated_rule_<n>_dispatch wrapper, not only
parse_generated_rule_0_dispatch. Derive expected rule indexes from the available
rule table or use a generic per-rule pattern, while permitting the documented
adaptive and precedence exceptions; retain the positive dispatch_generated_rule
check.

In `@README.md`:
- Around line 173-178: Update the generator revision description in README.md to
explicitly state that generated code retains both ATN-preference and
adaptive-routing exceptions, rather than mentioning only ATN-preference routing.
Keep the surrounding dispatch-table and runtime-owned guard description
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 69db191a-b8ea-43a6-aed4-873c564c0066

📥 Commits

Reviewing files that changed from the base of the PR and between 65cdc82 and d247f9d.

⛔ Files ignored due to path filters (13)
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__compact_left_recursive_rule_lifecycle.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__compact_ordinary_rule_lifecycle.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_module_file_header.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_checks.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_mismatch_diagnostic.snap is excluded by !**/*.snap
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-runtime/src/xpath/generated/x_path_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-toml-parser/src/generated/toml_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-toml-parser/src/generated/toml_parser.rs is excluded by !**/generated/**
  • docs/migration.md is excluded by !**/docs/**
📒 Files selected for processing (13)
  • README.md
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/src/parser/render/rules.rs
  • crates/antlr-rust-codegen/src/parser/routing.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/fixtures/revision9/codegen_api_lexer.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/fixtures/revision9/codegen_api_parser.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/semantics.rs
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-runtime/src/lib.rs
  • crates/antlr-rust-runtime/src/parser.rs
  • third_party/antlr-v4-grammar/self-hosted.sha256
💤 Files with no reviewable changes (2)
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/fixtures/revision9/codegen_api_lexer.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/fixtures/revision9/codegen_api_parser.rs

Comment thread crates/antlr-rust-codegen/src/generator/tests.rs
Comment thread crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/parser.rs Outdated
Comment thread README.md Outdated
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/antlr-rust-runtime/src/generated.rs 93.75% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Strengthen the generated deep-nesting fixture to detect any numeric per-rule dispatch wrapper while still permitting adaptive dispatch helpers. Document that both ATN-preference and adaptive-routing exceptions remain explicit.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/antlr-rust-codegen/src/generator/tests.rs 292 ⚪ 48 ⚪ 207 ⚪ 1332 (main: 1330) 🔴 0 ⚪
crates/antlr-rust-runtime/src/generated.rs 102 (main: 96) 🔴 14 (main: 10) 🔴 51 (main: 50) 🔴 101 (main: 92) 🔴 0 ⚪
crates/antlr-rust-codegen/src/parser/routing.rs 37 (main: 34) 🔴 25 (main: 26) 🟢 10 ⚪ 94 (main: 93) 🔴 8.60 (main: 9.57) 🔴
crates/antlr-rust-codegen/src/parser/render/rules.rs 45 ⚪ 52 ⚪ 5 ⚪ 113 (main: 114) 🟢 7.83 (main: 7.82) 🟢
crates/antlr-rust-runtime/src/lib.rs 4 ⚪ 3 ⚪ 1 ⚪ 7 ⚪ 29.69 (main: 28.88) 🟢

Generated by mehen v1.8.1 — the code quality watcher.

@tinovyatkin

Copy link
Copy Markdown
Contributor Author

Claude review completed cleanly at 1be9ffe5; no follow-up changes are needed. The CodeRabbit findings are resolved or withdrawn, and all exact-head checks are green.

@tinovyatkin
tinovyatkin merged commit 80cb700 into main Aug 11, 2026
27 checks passed
@tinovyatkin
tinovyatkin deleted the issue-322-dispatch-table branch August 11, 2026 23:10
@ophiarch ophiarch Bot mentioned this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegen: eliminate per-rule dispatch shims (856 six-line wrappers across checked-in parsers)

1 participant