Skip to content

refactor: use a virtual Cargo workspace root - #291

Merged
tinovyatkin merged 2 commits into
mainfrom
refactor/virtual-cargo-workspace
Aug 3, 2026
Merged

refactor: use a virtual Cargo workspace root#291
tinovyatkin merged 2 commits into
mainfrom
refactor/virtual-cargo-workspace

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • make the repository root a virtual Cargo workspace
  • move antlr-rust-runtime from the root src/ tree to crates/antlr-rust-runtime/, alongside the other published crates
  • move the ANTLR runtime integration suite from tools/ to tests/antlr-rust-runtime-testsuite/
  • update repository consumers, documentation, CI, packaging, and Release Please automation for the new layout
  • inherit the root license file into every published crate and verify required package artifacts during release preflight

This is a package and filesystem layout refactor. It does not change the generated-source/runtime contract, so __ANTLR4_RUST_CODEGEN_API is unchanged.

Testing

Check Result
cargo test --locked --workspace --all-features passed
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings passed
cargo +1.95 check --locked --workspace --all-targets --all-features passed
cargo fmt --all -- --check passed
actionlint passed
ANTLR conformance case LexerExec/KeywordID passed
Release package preflight for all publishable crates passed
Release Please schema and lockfile selector validation passed
Parity manifest metadata checks passed
Port-evidence validation (523 ledgers) passed
Parse benchmark tests (19 tests) passed

Move antlr-rust-runtime from the repository root into crates/antlr-rust-runtime so every published package has the same ownership boundary. Relocate the upstream conformance harness from tools/ into tests/ because it is integration-test infrastructure, while keeping it an opt-in standalone package.

Update temporary consumer manifests, parity and benchmark runners, CI and coverage paths, snapshots, port-evidence ledgers, package preflight scripts, and documentation. Adapt Release Please to the virtual manifest with a checked VERSION adapter and targeted workspace lockfile updates.

This changes repository paths only; the generated-source/runtime contract and codegen API revision remain unchanged.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Copy/Paste Detection

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

Show duplications

Found a 54 line (320 tokens) duplication in the following files:

  • Starting at line 606 of crates/antlr-rust-runtime/src/atn/lexer.rs
  • Starting at line 677 of crates/antlr-rust-runtime/src/atn/lexer.rs
    atn: &LexerAtn,
    hooks: &mut H,
    mut generated_action: A,
    mut generated_predicate: P,
    unknown_policy: UnknownSemanticPolicy,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    H: SemanticHooks,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction) -> bool,
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> Option<bool>,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    let hooks = RefCell::new(hooks);
    let token = next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut |lexer, action| {
            if !generated_action(lexer, action)
                && !dispatch_lexer_action_hook(&hooks, lexer, action)
                && unknown_policy == UnknownSemanticPolicy::Error
                && let (Ok(rule), Ok(index)) = (
                    usize::try_from(action.rule_index()),
                    usize::try_from(action.action_index()),
                )
            {
                lexer.record_semantic_error(true, rule, index);
            }
        },
        &mut |lexer, predicate| {
            generated_predicate(lexer, predicate)
                .or_else(|| dispatch_lexer_predicate_hook(&hooks, lexer, predicate))
                .unwrap_or_else(|| match unknown_policy {
                    UnknownSemanticPolicy::AssumeTrue => true,
                    UnknownSemanticPolicy::AssumeFalse => false,
                    UnknownSemanticPolicy::Error => {
                        lexer.record_semantic_error(
                            false,
                            predicate.rule_index(),
                            predicate.pred_index(),
                        );
                        false
                    }
                })
        },
        &mut |lexer| dispatch_lexer_before_token_hook(&hooks, lexer),
        &mut accept_adjuster,
        &mut |lexer, accept_position| {
            dispatch_lexer_after_accept_hook(&hooks, lexer, accept_position);
        },
        LexerMatchStrategy {
            compiled: None,
```rust

---

Found a 21 line (226 tokens) duplication in the following files:
* Starting at line 14965 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16370 of crates/antlr-rust-runtime/src/parser.rs

```rust
            (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)] {

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

  • Starting at line 16299 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17266 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 26 line (153 tokens) duplication in the following files:
* Starting at line 317 of crates/antlr-rust-runtime/src/atn/lexer.rs
* Starting at line 742 of crates/antlr-rust-runtime/src/atn/lexer.rs

```rust
pub fn next_token_with_hooks<I, A, P, E>(
    lexer: &mut BaseLexer<I>,
    sink: &mut TokenSink<'_>,
    atn: &LexerAtn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction),
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut |_| {},
        &mut accept_adjuster,
        &mut |_, _| {},
        LexerMatchStrategy {
            compiled: None,
            use_cache: false,

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

  • Starting at line 17787 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17921 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 25 line (142 tokens) duplication in the following files:
* Starting at line 450 of crates/antlr-rust-runtime/src/atn/lexer.rs
* Starting at line 559 of crates/antlr-rust-runtime/src/atn/lexer.rs

```rust
    atn: &LexerAtn,
    hooks: &mut H,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    H: SemanticHooks,
{
    let hooks = RefCell::new(hooks);
    let token = next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut |lexer, action| {
            let _ = dispatch_lexer_action_hook(&hooks, lexer, action);
        },
        &mut |lexer, predicate| {
            dispatch_lexer_predicate_hook(&hooks, lexer, predicate).unwrap_or(true)
        },
        &mut |lexer| dispatch_lexer_before_token_hook(&hooks, lexer),
        &mut |_, _, _| {},
        &mut |lexer, accept_position| {
            dispatch_lexer_after_accept_hook(&hooks, lexer, accept_position);
        },
        LexerMatchStrategy {
            compiled: None,

Found a 24 line (134 tokens) duplication in the following files:

  • Starting at line 3511 of crates/antlr-rust-runtime/src/atn/lexer.rs
  • Starting at line 1757 of crates/antlr-rust-runtime/src/atn/lexer_dfa.rs
        let atn = AtnDeserializer::new(&SerializedAtn::from_i32(&[
            4, 0, 2, // version, lexer, max token type
            9, // states
            6, -1, // 0 token start
            2, 0, // 1 rule 0 start
            1, 0, // 2
            1, 0, // 3
            7, 0, // 4 rule 0 stop
            2, 1, // 5 rule 1 start
            1, 1, // 6
            1, 1, // 7
            7, 1, // 8 rule 1 stop
            0, // non-greedy
            0, // precedence
            2, // rules
            1, 1, // rule 0 starts at 1, token type 1
            5, 2, // rule 1 starts at 5, token type 2
            1, // modes
            0, // default mode starts at 0
            0, // sets
            8, // edges
            0, 1, 1, 0, 0, 0, // start -> rule 0
            0, 5, 1, 0, 0, 0, // start -> rule 1
            1, 2, 5, 'a' as i32, 0, 0, 2, 3, 5, 'b' as i32, 0, 0, 3, 4, 1, 0, 0, 0, 5, 6, 5,
```rust

---

Found a 23 line (133 tokens) duplication in the following files:
* Starting at line 1056 of crates/antlr-rust-runtime/src/atn/lexer.rs
* Starting at line 1215 of crates/antlr-rust-runtime/src/atn/lexer.rs

```rust
        let source_has_semantic_context = dfa_state_has_semantic_context;
        for config in active {
            let Some(state) = atn.state(config.state) else {
                continue;
            };
            for transition in &state.transitions {
                if !transition.matches(symbol, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
                    continue;
                }
                let mut advanced = config.clone();
                set_config_state(atn, &mut advanced, transition.target());
                if symbol == EOF {
                    advanced.consumed_eof = true;
                } else {
                    advanced.position += 1;
                }
                next.push(advanced);
            }
        }

        let closure = epsilon_closure_with_lexer(lexer, atn, next, semantic_predicate);
        let target_has_semantic_context = closure.has_semantic_context;
        let suppress_edge = source_has_semantic_context || target_has_semantic_context;

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

  • Starting at line 15965 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 15990 of crates/antlr-rust-runtime/src/parser.rs
    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");
```rust

---

Found a 27 line (127 tokens) duplication in the following files:
* Starting at line 16420 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16492 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 22 line (125 tokens) duplication in the following files:

  • Starting at line 16446 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 16518 of crates/antlr-rust-runtime/src/parser.rs
            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,
```rust

---

Found a 18 line (122 tokens) duplication in the following files:
* Starting at line 3644 of crates/antlr-rust-runtime/src/atn/parser.rs
* Starting at line 3788 of crates/antlr-rust-runtime/src/atn/parser.rs

```rust
        atn.set_rule_to_stop_state(vec![7])
            .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::Epsilon { target: 2 })
            .expect("transition");
        atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 4 })
            .expect("transition");
        atn.add_transition(
            2,
            ParserTransitionSpec::Atom {
                target: 3,
                label: 1,
            },
        )
        .expect("transition");
        atn.add_transition(3, ParserTransitionSpec::Epsilon { target: 6 })

Found a 20 line (120 tokens) duplication in the following files:

  • Starting at line 284 of crates/antlr-rust-runtime/src/atn/mod.rs
  • Starting at line 939 of crates/antlr-rust-runtime/src/atn/parser_atn.rs
            Self::Range { start, stop, .. } => (*start..=*stop).contains(&symbol),
            Self::Set { set, .. } => set.contains(symbol),
            Self::NotSet { set, .. } => {
                (min_vocabulary..=max_vocabulary).contains(&symbol) && !set.contains(symbol)
            }
            Self::Wildcard { .. } => (min_vocabulary..=max_vocabulary).contains(&symbol),
            Self::Epsilon { .. }
            | Self::Rule { .. }
            | Self::Predicate { .. }
            | Self::Action { .. }
            | Self::Precedence { .. } => false,
        }
    }
}

/// Ordered set of integer intervals used by set and negated-set transitions.
///
/// Unicode grammars can contain very large ranges, so this stores normalized
/// intervals rather than expanding every code point into a flat set.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
```rust

---

Found a 34 line (119 tokens) duplication in the following files:
* Starting at line 10550 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 10625 of crates/antlr-rust-runtime/src/parser.rs

```rust
                        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);

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

  • Starting at line 320 of crates/antlr-rust-runtime/src/atn/lexer.rs
  • Starting at line 526 of crates/antlr-rust-runtime/src/atn/lexer.rs
  • Starting at line 745 of crates/antlr-rust-runtime/src/atn/lexer.rs
    atn: &LexerAtn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction),
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut |_| {},
        &mut accept_adjuster,
        &mut |_, _| {},
        LexerMatchStrategy {
            compiled: None,
```rust

---

Found a 25 line (116 tokens) duplication in the following files:
* Starting at line 123 of crates/antlr-rust-runtime/src/byte_stream.rs
* Starting at line 202 of crates/antlr-rust-runtime/src/char_stream.rs

```rust
impl<B: AsRef<[u8]>> IntStream for ByteStream<B> {
    fn consume(&mut self) {
        if !self.is_eof() {
            self.cursor += 1;
        }
    }

    fn la(&mut self, offset: isize) -> i32 {
        if offset == 0 {
            return 0;
        }

        // Mirror `InputStream::la`: `+1` is the symbol under the cursor, and
        // negative offsets look behind. `checked_*` keeps `isize::MIN` and
        // out-of-range lookahead on the EOF path instead of panicking.
        let absolute = if offset > 0 {
            self.cursor.checked_add((offset - 1).cast_unsigned())
        } else {
            offset
                .checked_neg()
                .and_then(|distance| usize::try_from(distance).ok())
                .and_then(|distance| self.cursor.checked_sub(distance))
        };

        absolute.map_or(EOF, |index| self.symbol_at(index).unwrap_or(EOF))

Found a 24 line (113 tokens) duplication in the following files:

  • Starting at line 406 of crates/antlr-rust-runtime/src/atn/parser.rs
  • Starting at line 3898 of crates/antlr-rust-runtime/src/atn/parser.rs
impl IntStream for LookaheadIntStream {
    fn consume(&mut self) {
        if self.la(1) != TOKEN_EOF {
            self.index += 1;
        }
    }

    fn la(&mut self, offset: isize) -> i32 {
        if offset <= 0 {
            return 0;
        }
        let offset = offset.cast_unsigned() - 1;
        self.symbols
            .get(self.index + offset)
            .copied()
            .unwrap_or(TOKEN_EOF)
    }

    fn index(&self) -> usize {
        self.index
    }

    fn seek(&mut self, index: usize) {
        self.index = index.min(self.symbols.len());
```rust

---

Found a 15 line (113 tokens) duplication in the following files:
* Starting at line 17846 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 18124 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 18 line (112 tokens) duplication in the following files:

  • Starting at line 14775 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17192 of crates/antlr-rust-runtime/src/parser.rs
            (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");
```rust

---

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

```rust
        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),

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

  • Starting at line 16966 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 17383 of crates/antlr-rust-runtime/src/parser.rs
    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))
```rust

---

Found a 22 line (111 tokens) duplication in the following files:
* Starting at line 14840 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16966 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 17383 of crates/antlr-rust-runtime/src/parser.rs

```rust
    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))

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

  • Starting at line 17516 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21461 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 2061 of crates/antlr-rust-runtime/src/lexer.rs
* Starting at line 2087 of crates/antlr-rust-runtime/src/lexer.rs

```rust
        let mut lexer = BaseLexer::new(InputStream::new("β"), data);
        lexer.consume_char();

        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
        let mut sink = TokenSink::new(&mut store);
        let id = lexer.eof_token(&mut sink).expect("test token should fit");
        let token = sink.view(id).expect("emitted token should exist");

        // byte_span is the field this test exists to pin and is absent from TokenView's Debug, so
        // snapshot the explicit (start, stop, text, byte_span) record rather than the token.
        insta::assert_compact_debug_snapshot!(
            (token.start(), token.stop(), token.text(), token.byte_span()),
            @r#"(1, 0, Some("<EOF>"), Some(2..2))"#

Found a 13 line (109 tokens) duplication in the following files:

  • Starting at line 17516 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21486 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);
```rust

---

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

```rust
    ) -> 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;

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

  • Starting at line 21757 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 21781 of crates/antlr-rust-runtime/src/parser.rs
    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)],
```rust

---

Found a 13 line (107 tokens) duplication in the following files:
* Starting at line 3021 of crates/antlr-rust-runtime/src/atn/lexer.rs
* Starting at line 3278 of crates/antlr-rust-runtime/src/atn/lexer.rs

```rust
        let mut hooks = LifecycleRecordingHooks::default();
        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
        let mut sink = TokenSink::new(&mut store);
        let mut ids = Vec::new();
        for _ in 0..3 {
            let id = if compiled {
                next_token_compiled_with_semantic_hooks(
                    &mut lexer, &mut sink, &atn, &dfa, &mut hooks,
                )
            } else {
                next_token_with_semantic_hooks(&mut lexer, &mut sink, &atn, &mut hooks)
            }
            .expect("lifecycle token should fit");

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

  • Starting at line 7399 of crates/antlr-rust-runtime/src/parser.rs
  • Starting at line 8094 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 15 line (106 tokens) duplication in the following files:
* Starting at line 2595 of crates/antlr-rust-runtime/src/atn/lexer.rs
* Starting at line 2643 of crates/antlr-rust-runtime/src/atn/lexer.rs

```rust
            (4, AtnStateKind::Basic, Some(1)),
            (5, AtnStateKind::RuleStop, Some(1)),
        ] {
            let mut state = LexerAtnState::new(state_number, kind);
            if let Some(rule_index) = rule_index {
                state = state.with_rule_index(rule_index);
            }
            atn.add_state(state);
        }
        atn.state_mut(0)
            .expect("token start")
            .add_transition(LexerTransition::Epsilon { target: 1 });
        atn.state_mut(0)
            .expect("token start")
            .add_transition(LexerTransition::Epsilon { target: 3 });

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

  • Starting at line 905 of crates/antlr-rust-runtime/src/atn/parser_atn.rs
  • Starting at line 1712 of crates/antlr-rust-runtime/src/atn/parser_atn.rs
impl ParserTransitionData<'_> {
    pub const fn target(self) -> usize {
        match self {
            Self::Epsilon { target }
            | Self::Atom { target, .. }
            | Self::Range { target, .. }
            | Self::Set { target, .. }
            | Self::NotSet { target, .. }
            | Self::Wildcard { target }
            | Self::Rule { target, .. }
            | Self::Predicate { target, .. }
            | Self::Action { target, .. }
            | Self::Precedence { target, .. } => target,
        }
    }
```rust

---

Found a 13 line (104 tokens) duplication in the following files:
* Starting at line 14953 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 16357 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 14 line (102 tokens) duplication in the following files:

  • Starting at line 1668 of crates/antlr-rust-runtime/src/atn/lexer_dfa.rs
  • Starting at line 1686 of crates/antlr-rust-runtime/src/atn/lexer_dfa.rs
        let id = next_token_compiled(lexer, &mut sink, atn, dfa).expect("test token should fit");
        let token = sink.view(id).expect("emitted token should exist");
        TokenSnapshot {
            token_type: token.token_type(),
            text: token.text_or_empty().to_owned(),
            channel: token.channel(),
            start: token.start(),
            stop: token.stop(),
            start_byte: token.start_byte(),
            stop_byte: token.stop_byte(),
            line: token.line(),
            column: token.column(),
        }
    }
```rust

---

Found a 13 line (100 tokens) duplication in the following files:
* Starting at line 7050 of crates/antlr-rust-runtime/src/parser.rs
* Starting at line 7074 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;
            };

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude Code is working…

I'll analyze this and get back to you.

View job run

@coderabbitai

coderabbitai Bot commented Aug 3, 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: 39 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: d75217cd-b6a5-46e1-b67e-c311572653b1

📥 Commits

Reviewing files that changed from the base of the PR and between 2aa2fa9 and f0cf8f9.

📒 Files selected for processing (6)
  • Cargo.toml
  • crates/antlr-rust-codegen/Cargo.toml
  • crates/antlr-rust-g4-parser/Cargo.toml
  • crates/antlr-rust-rs-parser/Cargo.toml
  • crates/antlr-rust-runtime/Cargo.toml
  • tools/release/verify-package-contents.sh
📝 Walkthrough

Walkthrough

The pull request moves the runtime into crates/antlr-rust-runtime, adds parser and lexer runtime implementations, introduces tree and stream APIs, relocates the conformance testsuite under tests/, and updates workspace, tooling, release, CI, documentation, and fixture paths.

Changes

Runtime workspace integration

Layer / File(s) Summary
Workspace layout and path migration
.github/workflows/*, Cargo.toml, release-please-config.json, tools/*, tests/*, README.md, AGENTS.md, CLAUDE.md
Workspace members, dependency paths, CI filters, coverage paths, release configuration, fixtures, documentation, and tooling now reference the relocated runtime crate and testsuite.
Runtime crate contracts and data models
crates/antlr-rust-runtime/Cargo.toml, crates/antlr-rust-runtime/src/{lib,int_stream,char_stream,byte_stream,token,token_stream,vocabulary,errors,generated,recognizer}.rs
The runtime crate defines public stream, token, vocabulary, recognizer, error, generated-code, and root-export APIs.
Parser ATN, prediction, and DFA runtime
crates/antlr-rust-runtime/src/atn/{parser,parser_atn,serialized,bypass}.rs, crates/antlr-rust-runtime/src/{dfa,prediction,perf}.rs
Packed parser ATNs, deserialization, prediction contexts, parser DFA storage, ATN simulation, rule bypass alternatives, and performance counters are implemented.
Lexer ATN and compiled DFA runtime
crates/antlr-rust-runtime/src/atn/{mod,lexer,lexer_dfa,ascii_range}.rs, crates/antlr-rust-runtime/src/{lexer,semir}.rs
Lexer interpretation, semantic actions and predicates, token lifecycle handling, compiled DFA matching, serialization, continuations, ASCII scanning, and related tests are added.
Parse trees, patterns, and XPath
crates/antlr-rust-runtime/src/{tree,tree_pattern,xpath}.rs, crates/antlr-rust-runtime/src/xpath/XPathLexer.g4
Flat parse-tree storage, traversal, pattern matching, bypass-ATN compilation, XPath queries, and Unicode-aware XPath lexing are added.
Semantic IR and conformance testsuite
crates/antlr-rust-runtime/src/semir.rs, tests/antlr-rust-runtime-testsuite/*
Semantic predicates and actions use an arena-backed IR. The conformance harness gains workspace-root handling, Rust naming utilities, a render driver, and Rust templates.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: refactoring the repository to use a virtual Cargo workspace root.
✨ 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 refactor/virtual-cargo-workspace

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.

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2aa2fa9619

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

license.workspace = true
keywords = ["antlr", "antlr4", "parser", "lexer", "runtime"]
categories = ["parser-implementations", "development-tools", "parsing"]
include = ["/Cargo.toml", "/README.md", "/src/**"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the license file in the runtime package

When antlr-rust-runtime is published from its new subdirectory, this include list cannot capture the repository-root LICENSE; a cargo package reproduction contains the README and sources but no license file. The previous root manifest explicitly included /LICENSE, so this silently removes the BSD license text from future runtime archives; copy or otherwise package the license within the crate boundary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f0cf8f9. The workspace now declares license-file = "LICENSE", and every publishable member inherits it alongside the SPDX license field. Cargo 1.95 packages the root file as LICENSE in each crate archive. I also made verify-package-contents.sh require both LICENSE and README.md; the full four-crate package preflight passes.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/antlr-rust-runtime/src/parser.rs 2415 🆕 1598 🆕 772 🆕 5407 🆕 0 🆕
crates/antlr-rust-runtime/src/lexer.rs 336 🆕 76 🆕 206 🆕 570 🆕 0 🆕
crates/antlr-rust-runtime/src/atn/parser_atn.rs 628 🆕 308 🆕 204 🆕 926 🆕 0 🆕
crates/antlr-rust-runtime/src/tree.rs 339 🆕 92 🆕 176 🆕 473 🆕 0 🆕
crates/antlr-rust-runtime/src/atn/parser.rs 415 🆕 298 🆕 136 🆕 1173 🆕 0 🆕
crates/antlr-rust-runtime/src/atn/lexer.rs 467 🆕 347 🆕 120 🆕 1030 🆕 0 🆕
crates/antlr-rust-runtime/src/prediction.rs 301 🆕 186 🆕 119 🆕 629 🆕 0 🆕
crates/antlr-rust-runtime/src/atn/lexer_dfa.rs 445 🆕 286 🆕 98 🆕 835 🆕 0 🆕
crates/antlr-rust-runtime/src/token.rs 184 🆕 49 🆕 96 🆕 207 🆕 0 🆕
crates/antlr-rust-runtime/src/dfa.rs 201 🆕 78 🆕 94 🆕 369 🆕 0 🆕
crates/antlr-rust-runtime/src/semir.rs 208 🆕 30 🆕 83 🆕 322 🆕 0 🆕
crates/antlr-rust-runtime/src/tree_pattern.rs 211 🆕 93 🆕 76 🆕 351 🆕 0 🆕
crates/antlr-rust-runtime/src/token_stream.rs 129 🆕 38 🆕 67 🆕 191 🆕 0 🆕
crates/antlr-rust-runtime/src/perf.rs 126 🆕 32 🆕 52 🆕 179 🆕 0 🆕
crates/antlr-rust-runtime/src/recognizer.rs 53 🆕 2 🆕 39 🆕 77 🆕 6.87 🆕
crates/antlr-rust-runtime/src/xpath.rs 131 🆕 52 🆕 39 🆕 140 🆕 0 🆕
crates/antlr-rust-runtime/src/char_stream.rs 82 🆕 29 🆕 37 🆕 117 🆕 2.73 🆕
crates/antlr-rust-runtime/src/atn/serialized.rs 298 🆕 217 🆕 36 🆕 304 🆕 0 🆕
crates/antlr-rust-runtime/src/atn/mod.rs 55 🆕 13 🆕 34 🆕 57 🆕 7.28 🆕
crates/antlr-rust-runtime/src/atn/bypass.rs 105 🆕 82 🆕 25 🆕 244 🆕 0 🆕
crates/antlr-rust-runtime/src/byte_stream.rs 44 🆕 15 🆕 22 🆕 98 🆕 11.30 🆕
crates/antlr-rust-runtime/src/atn/ascii_range.rs 61 🆕 58 🆕 15 🆕 116 🆕 9.42 🆕
crates/antlr-rust-runtime/src/generated.rs 18 🆕 0 🆕 12 🆕 19 🆕 23.50 🆕
crates/antlr-rust-runtime/src/vocabulary.rs 34 🆕 3 🆕 11 🆕 38 🆕 19.01 🆕
crates/antlr-rust-runtime/src/int_stream.rs 5 🆕 0 🆕 3 🆕 2 🆕 53.51 🆕
crates/antlr-rust-runtime/src/errors.rs 6 🆕 0 🆕 2 🆕 2 🆕 33.06 🆕
crates/antlr-rust-runtime/src/lib.rs 4 🆕 3 🆕 1 🆕 7 🆕 30.02 🆕

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

@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: 23

🤖 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-runtime/src/atn/bypass.rs`:
- Around line 199-210: In the rule-end-state collection loop, add a debug
assertion before assigning bypass_stop_for_end[end_state] to verify that the
entry for end_state is still None. Anchor the check in the rule_end_state and
bypass_stop_for_end handling, preserving the existing retarget map construction
while catching duplicate rule end states during tests.

In `@crates/antlr-rust-runtime/src/atn/lexer.rs`:
- Around line 3052-3072: Replace the complete token-tuple and lifecycle-event
assertions in crates/antlr-rust-runtime/src/atn/lexer.rs lines 3052-3072 with
appropriate insta debug snapshot assertions, while retaining explicit
interpreted-versus-compiled equivalence assertions; add
#[allow(clippy::disallowed_methods)] to its tests module. Also replace the
complete error-message list assertion in
crates/antlr-rust-runtime/src/atn/lexer_dfa.rs lines 2204-2207 with an insta
snapshot assertion and add the same allowance to that tests module.
- Around line 1086-1090: Collapse the nested conditions in the lexer DFA
edge-recording logic so the operation remains guarded by both !suppress_edge and
symbol != EOF. Update the surrounding branch in the lexer method containing
record_lexer_dfa_edge to satisfy clippy without changing behavior.

In `@crates/antlr-rust-runtime/src/atn/parser_atn.rs`:
- Around line 1350-1367: Update transitions_from to retrieve transitions through
the existing transitions_by_source index instead of scanning self.transitions
and filtering by source. Preserve its DoubleEndedIterator return type and
insertion-order semantics, including next() and next_back(), so
mark_precedence_decisions and add_parser_rule_return_edges retain their current
behavior.

In `@crates/antlr-rust-runtime/src/byte_stream.rs`:
- Around line 249-251: Update the #[allow(clippy::disallowed_methods)] attribute
on the tests module to match actual usage: remove it if clippy does not report a
violation, otherwise revise its comment to explain the module’s expect call
rather than referencing nonexistent insta macros.

In `@crates/antlr-rust-runtime/src/char_stream.rs`:
- Around line 351-382: Add assertions to
optional_fast_paths_preserve_scalar_indexes_and_positions for the Unicode
InputStream covering byte_interval and text_source_interval on "aβ\nγ",
including an interval ending at the final scalar so the source.len() fallback
and byte_offsets multi-byte boundary calculations are verified.
- Around line 318-328: Remove the custom byte_interval override and rely on the
trait default that derives byte bounds from text_source_interval. Keep the
existing text_source_interval implementation as the single source for
empty-interval handling and index clamping, ensuring token text and byte spans
remain consistent.

In `@crates/antlr-rust-runtime/src/dfa.rs`:
- Around line 364-376: Propagate the existing change result from
EdgeTable::update_sparse through EdgeTable::add instead of discarding it. Update
add_edge to use the returned bool directly for bump_learning_revision, removing
both pre- and post-add edge lookups while preserving false for unchanged targets
or rejected symbols.

In `@crates/antlr-rust-runtime/src/errors.rs`:
- Around line 36-57: add a public constructor or builder on SyntaxErrorEvent
that accepts all intended diagnostic fields, including offending, line, column,
span, message, and error. Document its usage for external TokenSource
implementations and recognizers invoking the public notify_error_listeners API,
while preserving the #[non_exhaustive] struct design.

In `@crates/antlr-rust-runtime/src/lib.rs`:
- Around line 72-76: Update the crate-root token re-export list in lib.rs to
include TokenSourceError alongside TokenSource and the other public token
symbols, so external users can name the type exposed by TokenSource and
SyntaxErrorEvent APIs.

In `@crates/antlr-rust-runtime/src/prediction.rs`:
- Around line 2374-2429: Replace the hand-written per-case expected DOT
constants and comparison in pinned_upstream_test_graph_nodes_matches_dot with
insta::assert_snapshot! keyed by case.logical_id, while preserving the
inventory, uniqueness, logical-id, and selector assertions. Remove only the
obsolete expected-string storage and mismatch aggregation, and add
#[allow(clippy::disallowed_methods)] to the upstream_graph_nodes module.

In `@crates/antlr-rust-runtime/src/recognizer.rs`:
- Around line 62-68: Document on RecognizerData or its error_listeners field
that cloning creates separate listener lists sharing the same listener instances
via Arc. Add a test covering a registered listener before cloning, then trigger
recording through both recognizers and assert both writes reach the same shared
buffer.
- Around line 18-23: Update the lock acquisition in the error-listener
forwarding method syntax_error to recover the mutex’s inner listener value when
the lock is poisoned instead of calling expect and panicking. Preserve the
existing delegation to self.0’s syntax_error with the recovered guard for both
healthy and poisoned locks.

In `@crates/antlr-rust-runtime/src/token.rs`:
- Around line 384-394: Add TokenStoreErrorKind::MissingSource and
TokenStoreErrorKind::InvalidByteSpan { start, stop, source_len }, including
matching Display formatting. In the source-backed validation flow, replace the
overflow error for absent self.source with MissingSource, and distinguish
start_byte > stop_byte by returning InvalidByteSpan with the span values; retain
the existing overflow error for stop_byte exceeding source.len().
- Around line 868-894: Convert the complete rendered-string assertions in
token_view_display_matches_antlr_shape and
synthetic_token_display_uses_antlr_negative_index to insta snapshots, using
assert_snapshot! for display output and assert_debug_snapshot! for the complete
ordered token collection around the collected-list test. Preserve the existing
property assertions for synthetic status and add
#[allow(clippy::disallowed_methods)] to the test module.

In `@crates/antlr-rust-runtime/src/tree_pattern.rs`:
- Around line 1365-1378: Update the state-range comment above the table in the
fixture ATN builder to match the actual assignments: stat states 0..=4 and expr
states 5..=10, consistent with set_rule_to_start_state and
set_rule_to_stop_state.
- Around line 677-682: Update rekey_tags_by_token_id to return a
Result<BTreeMap<TokenId, TagInfo>, ParseTreePatternError> instead of dropping
failed TokenId conversions through filter_map. Convert each index with error
propagation, preserving all successful entries, and update interpret to
propagate the returned ParseTreePatternError through the compile path.

In `@crates/antlr-rust-runtime/src/tree.rs`:
- Around line 1543-1546: Replace the inline to_string_tree_with_names output
assertions in stores_rule_children_in_one_pooled_range with insta snapshots,
covering the dumps currently checked at the referenced assertions. Keep explicit
assertions for properties, bounds, round-trip invariants, and ordering checks,
including the ordering assertions near the end of the test.

In `@crates/antlr-rust-runtime/src/xpath.rs`:
- Around line 284-298: Update compile_element so only LexemeKind::Identifier
falls through to resolve_rule; explicitly return XPathError::UnknownPathElement
for delimiter lexemes such as Anywhere, Root, and Bang encountered in a word
position. Preserve the existing wildcard, string, uppercase-identifier, and
valid rule-name handling, then regenerate the upstream_invalid_paths snapshot.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg`:
- Around line 178-193: Update LeafListener to store an injected output sink
instead of calling self.output(), since listener callbacks cannot access the
recognizer. Replace the affected visit_terminal and exit_* callback output calls
to use that sink, and update WalkListener to construct LeafListener with the
sink. Apply this consistently to every generated listener implementation in the
template.
- Around line 232-237: Update the token output in the relevant writeln! call
within the Rust test template to invoke text_or_empty() on the result of
ctx.id_token().expect("ID alternative").symbol(), matching the neighboring
token-printing logic and emitting token text rather than the token value.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg.design-notes.md`:
- Around line 212-272: Fix the markdownlint issues in the “Runtime API surface
this assumes” section: add blank lines after the headings identified by MD022,
and preserve the intentional global numbering for the capability checklist while
resolving MD029 by configuring ordered-list numbering for this file or making
the checklist one continuous list under a single heading.
- Around line 125-210: Update the conflicting per-template notes in the design
document to match the corrected renderings in the reference corrections section
and Rust.test.stg, including Assert, Append, member initializers,
RuleInvocationStack, ParserPropertyMember, TreeNodeWithAltNumField,
WalkListener, and Cast. Alternatively, clearly label the entire notes section as
a pre-correction record, consistent with rust-test-stg-honest-reference-gap.md.
🪄 Autofix (Beta)

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: e3e3f702-6e50-44f4-92b7-12462823231a

📥 Commits

Reviewing files that changed from the base of the PR and between a44f645 and 2aa2fa9.

⛔ Files ignored due to path filters (40)
  • crates/antlr-rust-runtime/src/atn/snapshots/antlr4_runtime__atn__parser__tests__adaptive_predict_marks_sll_conflict_for_full_context.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/atn/snapshots/antlr4_runtime__atn__parser__tests__adaptive_predict_stream_retries_full_context_conflict.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/atn/snapshots/antlr4_runtime__atn__parser__tests__context_prediction_reports_context_sensitivity_for_dfa_conflict.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/atn/snapshots/antlr4_runtime__atn__serialized__tests__reads_small_parser_atn.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__committed_bail_error_notifies_error_listener.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__committed_left_recursive_depth_cap_keeps_listener_events_balanced.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__committed_predicate_star_loop_uses_single_token_deletion.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__committed_walker_dispatches_recovery_diagnostics.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__deferred_alternatives_preserve_left_recursive_contexts.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__failed_interpreted_parse_notifies_error_listener.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__fast_recognizer_preserves_labeled_left_recursive_operator_context.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__folds_left_recursive_boundary_into_rule_node.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__generated_prediction_diagnostics_use_adaptive_context.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__parsed_file_exposes_all_buffered_tokens.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__parser_dispatches_recovery_diagnostics_through_registered_listeners.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__private_context_alt_tracking_keeps_fast_predicate_recognition.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__parser__tests__recovery_diagnostics_expose_the_offending_token_to_listeners.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__recognizer__tests__recognizers_replace_the_default_console_error_listener.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree__tests__terminal_children_raw_vs_labeled.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_custom_delimiters.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_interleaves_text_and_tags.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_no_tags.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_parses_labeled_tags.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_rejects_malformed.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__tree_pattern__tests__split_strips_escapes.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__vocabulary__tests__upstream_vocabulary__vocabulary_from_token_names_matches_java.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__xpath__tests__lexer_and_parser_edge_cases.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__xpath__tests__named_anywhere_inversion_matches_java_4_13_2.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__xpath__tests__upstream_invalid_paths.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__xpath__tests__upstream_valid_paths.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/snapshots/antlr4_runtime__xpath__tests__wildcard_anywhere_virtual_root.snap is excluded by !**/*.snap
  • crates/antlr-rust-runtime/src/xpath/generated/semantics.json is excluded by !**/generated/**
  • crates/antlr-rust-runtime/src/xpath/generated/x_path_lexer.rs is excluded by !**/generated/**
  • docs/antlr4-rust-gen-refactoring-plan.md is excluded by !**/docs/**
  • docs/honest-action-transpiler-plan-concerns.md is excluded by !**/docs/**
  • docs/honest-action-transpiler-plan.md is excluded by !**/docs/**
  • docs/issue-141-direct-g4-codegen-plan.md is excluded by !**/docs/**
  • docs/issue-9-semantic-predicates-actions-design.md is excluded by !**/docs/**
  • docs/perf-issue-15-csharp-gap-plan.md is excluded by !**/docs/**
  • docs/runtime-testsuite.md is excluded by !**/docs/**
📒 Files selected for processing (95)
  • .github/workflows/ci.yml
  • .typos.toml
  • AGENTS.md
  • CLAUDE.md
  • Cargo.toml
  • README.md
  • VERSION
  • codecov.yml
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/multi_recognizer.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/support.rs
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-9ea85e6b69/revisions/testgraphnodes-test-9ea85e6b69-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-a-429589e373/revisions/testgraphnodes-test-a-a-429589e373-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-a-fullctx-b023f64b6c/revisions/testgraphnodes-test-a-a-fullctx-b023f64b6c-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-ax-fd976a340d/revisions/testgraphnodes-test-a-ax-fd976a340d-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-ax-fullctx-502155fcf9/revisions/testgraphnodes-test-a-ax-fullctx-502155fcf9-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-b-080058428f/revisions/testgraphnodes-test-a-b-080058428f-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-bx-b15f7b876f/revisions/testgraphnodes-test-a-bx-b15f7b876f-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-a-bx-fullctx-a35242b6cf/revisions/testgraphnodes-test-a-bx-fullctx-a35242b6cf-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aa-a-fullctx-8e728ea773/revisions/testgraphnodes-test-aa-a-fullctx-8e728ea773-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aa-aa-0a175c83db/revisions/testgraphnodes-test-aa-aa-0a175c83db-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aa-abc-db12d99894/revisions/testgraphnodes-test-aa-abc-db12d99894-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aab-aa-d90d8d54f0/revisions/testgraphnodes-test-aab-aa-d90d8d54f0-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aab-ab-e2d46352b4/revisions/testgraphnodes-test-aab-ab-e2d46352b4-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aab-ac-139c5b709d/revisions/testgraphnodes-test-aab-ac-139c5b709d-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aac-ab-ef785e17e7/revisions/testgraphnodes-test-aac-ab-ef785e17e7-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaubu-acudu-7cb798b616/revisions/testgraphnodes-test-aaubu-acudu-7cb798b616-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaubv-abvdu-ecc8850384/revisions/testgraphnodes-test-aaubv-abvdu-ecc8850384-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaubv-abvdx-01eb5714fe/revisions/testgraphnodes-test-aaubv-abvdx-01eb5714fe-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaubv-abwdx-7953c9b489/revisions/testgraphnodes-test-aaubv-abwdx-7953c9b489-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaubv-acwdx-f479c849df/revisions/testgraphnodes-test-aaubv-acwdx-f479c849df-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aax-aay-c0f9b80842/revisions/testgraphnodes-test-aax-aay-c0f9b80842-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aax-aby-cccf935759/revisions/testgraphnodes-test-aax-aby-cccf935759-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aaxc-aayd-a73533f64d/revisions/testgraphnodes-test-aaxc-aayd-a73533f64d-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-abx-abx-77366e32e9/revisions/testgraphnodes-test-abx-abx-77366e32e9-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-abx-acx-a3af7f90fa/revisions/testgraphnodes-test-abx-acx-a3af7f90fa-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-aex-bfx-07ad9de126/revisions/testgraphnodes-test-aex-bfx-07ad9de126-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-a-62a48f251b/revisions/testgraphnodes-test-ax-a-62a48f251b-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-a-fullctx-7ef9c1d6b2/revisions/testgraphnodes-test-ax-a-fullctx-7ef9c1d6b2-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-ax-48f57578fa/revisions/testgraphnodes-test-ax-ax-48f57578fa-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-ax-same-1504dc3dd3/revisions/testgraphnodes-test-ax-ax-same-1504dc3dd3-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-bx-1ea2df9a04/revisions/testgraphnodes-test-ax-bx-1ea2df9a04-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-bx-same-d0506bf7a9/revisions/testgraphnodes-test-ax-bx-same-d0506bf7a9-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-ax-by-47815d59d2/revisions/testgraphnodes-test-ax-by-47815d59d2-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-fullctx-3a6b2d8201/revisions/testgraphnodes-test-fullctx-3a6b2d8201-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-x-546922b23c/revisions/testgraphnodes-test-x-546922b23c-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testgraphnodes-test-x-fullctx-7fdaaf473e/revisions/testgraphnodes-test-x-fullctx-7fdaaf473e-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testvocabulary-testemptyvocabulary-66d31ad014/revisions/testvocabulary-testemptyvocabulary-66d31ad014-r1/manifest.json
  • crates/antlr-rust-codegen/tests/codegen-direct/port-evidence/testvocabulary-testvocabularyfromtokennames-d047506a84/revisions/testvocabulary-testvocabularyfromtokennames-d047506a84-r1/manifest.json
  • crates/antlr-rust-runtime/Cargo.toml
  • crates/antlr-rust-runtime/src/atn/ascii_range.rs
  • crates/antlr-rust-runtime/src/atn/bypass.rs
  • crates/antlr-rust-runtime/src/atn/lexer.rs
  • crates/antlr-rust-runtime/src/atn/lexer_dfa.rs
  • crates/antlr-rust-runtime/src/atn/mod.rs
  • crates/antlr-rust-runtime/src/atn/parser.rs
  • crates/antlr-rust-runtime/src/atn/parser_atn.rs
  • crates/antlr-rust-runtime/src/atn/serialized.rs
  • crates/antlr-rust-runtime/src/byte_stream.rs
  • crates/antlr-rust-runtime/src/char_stream.rs
  • crates/antlr-rust-runtime/src/dfa.rs
  • crates/antlr-rust-runtime/src/errors.rs
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-runtime/src/int_stream.rs
  • crates/antlr-rust-runtime/src/lexer.rs
  • crates/antlr-rust-runtime/src/lib.rs
  • crates/antlr-rust-runtime/src/parser.rs
  • crates/antlr-rust-runtime/src/perf.rs
  • crates/antlr-rust-runtime/src/prediction.rs
  • crates/antlr-rust-runtime/src/recognizer.rs
  • crates/antlr-rust-runtime/src/semir.rs
  • crates/antlr-rust-runtime/src/token.rs
  • crates/antlr-rust-runtime/src/token_stream.rs
  • crates/antlr-rust-runtime/src/tree.rs
  • crates/antlr-rust-runtime/src/tree_pattern.rs
  • crates/antlr-rust-runtime/src/vocabulary.rs
  • crates/antlr-rust-runtime/src/xpath.rs
  • crates/antlr-rust-runtime/src/xpath/XPathLexer.g4
  • release-please-config.json
  • tests/antlr-rust-runtime-testsuite/Cargo.toml
  • tests/antlr-rust-runtime-testsuite/java/RenderGrammar.java
  • tests/antlr-rust-runtime-testsuite/src/main.rs
  • tests/antlr-rust-runtime-testsuite/src/rust_names.rs
  • tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg
  • tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg.design-notes.md
  • tests/antlr-rust-runtime-testsuite/templates/rust-test-stg-honest-reference-gap.md
  • tests/javascript-parity/dumper/Cargo.toml
  • tests/kotlin-parity/dumper/Cargo.toml
  • tests/typescript-parity/dumper/Cargo.toml
  • tool/resources/org/antlr/v4/tool/templates/codegen/Rust/Rust.stg
  • tools/fixed-lookahead-bench/run.sh
  • tools/grammar-frontend/generate-port-evidence.mjs
  • tools/parse-bench/run.py
  • tools/parse-bench/test_run.py
  • tools/release/check-workspace-version.sh
  • tools/release/preflight-package-archives.sh

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (23)
crates/antlr-rust-runtime/src/atn/bypass.rs (1)

199-210: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Assert that each rule has a distinct end state before building the retarget map.

bypass_stop_for_end[end_state] = Some(bypass_stop) overwrites the entry when two rules resolve to the same end state. The retarget pass at Line 227 then routes the first rule's incoming edges through the second rule's bypass stop, which produces a silently wrong parse tree rather than an error.

Deserialized ATNs derive one RuleStop per rule, so the assumption holds there. ParserAtnBuilder::set_rule_to_stop_state accepts duplicates, and left_recursive_loop_entry_atn in crates/antlr-rust-runtime/src/atn/parser.rs (Line 3863) already builds an ATN with vec![7, 7]. Add a debug assertion so the assumption is stated and caught in tests.

🛡️ Proposed defensive assertion
             let (end_state, exclude) = self.rule_end_state(rule)?;
             end_states.push(end_state);
+            debug_assert!(
+                bypass_stop_for_end[end_state].is_none(),
+                "rule {rule} shares end state {end_state} with an earlier rule; \
+                 bypass retargeting requires one end state per rule"
+            );
             bypass_stop_for_end[end_state] = Some(bypass_stop);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let mut end_states: Vec<usize> = Vec::with_capacity(rule_count);
        let mut bypass_stop_for_end: Vec<Option<usize>> = vec![None; self.kinds.len()];
        let mut excluded: BTreeSet<(usize, usize)> = BTreeSet::new();
        for rule in 0..rule_count {
            let bypass_stop = new_state_base + rule * 3 + 1;
            let (end_state, exclude) = self.rule_end_state(rule)?;
            end_states.push(end_state);
            debug_assert!(
                bypass_stop_for_end[end_state].is_none(),
                "rule {rule} shares end state {end_state} with an earlier rule; \
                 bypass retargeting requires one end state per rule"
            );
            bypass_stop_for_end[end_state] = Some(bypass_stop);
            if let Some(exclude) = exclude {
                excluded.insert(exclude);
            }
        }
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/atn/bypass.rs` around lines 199 - 210, In the
rule-end-state collection loop, add a debug assertion before assigning
bypass_stop_for_end[end_state] to verify that the entry for end_state is still
None. Anchor the check in the rule_end_state and bypass_stop_for_end handling,
preserving the existing retarget map construction while catching duplicate rule
end states during tests.
crates/antlr-rust-runtime/src/atn/lexer.rs (2)

1086-1090: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm no other nested-if sites in the touched lexer files and check clippy config.
fd -H -t f 'clippy.toml|Cargo.toml' -d 2 --exec sh -c 'echo "== $1"; grep -n -A5 "lints\|clippy" "$1" | head -40' sh {}
rg -nP -U '(?s)if\s+!?[a-z_]+\s*\{\s*\n\s*if\s' crates/antlr-rust-runtime/src/atn/lexer.rs crates/antlr-rust-runtime/src/atn/lexer_dfa.rs

Repository: ophi-dev/antlr-rust-runtime

Length of output: 2164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'pwd: '; pwd
printf '\nTop-level files: '; git ls-files | sed -n '1,120p'

printf '\nTarget lexer outline/size: '
wc -l crates/antlr-rust-runtime/src/atn/lexer.rs crates/antlr-rust-runtime/src/atn/lexer_dfa.rs 2>/dev/null || true

printf '\nRelevant code slices: '
sed -n '1076,1096p' crates/antlr-rust-runtime/src/atn/lexer.rs 2>/dev/null || true
sed -n '1258,1278p' crates/antlr-rust-runtime/src/atn/lexer.rs 2>/dev/null || true

printf '\nClippy/toml lints references: '
fd -H -t f 'clippy.toml|Cargo.toml' -d 2 --exec sh -c 'echo "== $1"; grep -n -A7 -B2 -i "lints\|clippy" "$1" | head -80' sh {} 2>/dev/null

printf '\nNested if search lexical approximation: '
python3 - <<'PY'
from pathlib import Path
for p in ['crates/antlr-rust-runtime/src/atn/lexer.rs','crates/antlr-rust-runtime/src/atn/lexer_dfa.rs']:
    path=Path(p)
    if not path.exists(): continue
    lines=path.read_text().splitlines()
    for i,l in enumerate(lines):
        if l.lstrip().startswith('if ') and '{' in l:
            start=i+1
            j=k=0
            for nxt in range(start, min(len(lines), start+10)):
                line=lines[nxt].split('///')[0] # ignore trailing comments for brace count
                j+=line.count('{')-line.count('}')
                if '{' in line and line.strip()[:4]=='if ':
                    print(f'{p}:{start}:{nxt+1}: outer at line {start}, inner at line {nxt+1}')
                    break
PY

Repository: ophi-dev/antlr-rust-runtime

Length of output: 2135


Collapse the nested if before pushing.

clippy::collapsible_if is warn by default, and CI treats clippy warnings as errors for Rust. Combine the condition so crates/antlr-rust-runtime/src/atn/lexer.rs does not fail clippy.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/atn/lexer.rs` around lines 1086 - 1090,
Collapse the nested conditions in the lexer DFA edge-recording logic so the
operation remains guarded by both !suppress_edge and symbol != EOF. Update the
surrounding branch in the lexer method containing record_lexer_dfa_edge to
satisfy clippy without changing behavior.

Source: Coding guidelines


3052-3072: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Apply the insta snapshot guideline to tests that pin complete collections. Both test modules pin whole ordered collections with hand-written assertions. The shared root cause is that the repository snapshot guideline is not applied to these value pins. Keep the interpreted-versus-compiled equivalence assertions as explicit assertions, because they check an invariant.

  • crates/antlr-rust-runtime/src/atn/lexer.rs#L3052-L3072: replace the full lifecycle-event and token-tuple pins with assert_debug_snapshot! or assert_compact_debug_snapshot!, and add #[allow(clippy::disallowed_methods)] on the tests module.
  • crates/antlr-rust-runtime/src/atn/lexer_dfa.rs#L2204-L2207: replace the full error-message list pin with assert_debug_snapshot! or assert_compact_debug_snapshot!, and add #[allow(clippy::disallowed_methods)] on the tests module.

As per coding guidelines: "Use insta snapshots instead of hand-written assertions when tests pin complete values, collections, diagnostics, generated code, or token/tree/ATN/DFA dumps" and "Every test module or bare test function invoking an insta macro must add #[allow(clippy::disallowed_methods)]".

📍 Affects 2 files
  • crates/antlr-rust-runtime/src/atn/lexer.rs#L3052-L3072 (this comment)
  • crates/antlr-rust-runtime/src/atn/lexer_dfa.rs#L2204-L2207
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/atn/lexer.rs` around lines 3052 - 3072, Replace
the complete token-tuple and lifecycle-event assertions in
crates/antlr-rust-runtime/src/atn/lexer.rs lines 3052-3072 with appropriate
insta debug snapshot assertions, while retaining explicit
interpreted-versus-compiled equivalence assertions; add
#[allow(clippy::disallowed_methods)] to its tests module. Also replace the
complete error-message list assertion in
crates/antlr-rust-runtime/src/atn/lexer_dfa.rs lines 2204-2207 with an insta
snapshot assertion and add the same allowance to that tests module.

Source: Coding guidelines

crates/antlr-rust-runtime/src/atn/parser_atn.rs (1)

1350-1367: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

transitions_from scans the whole transition pool, making ATN construction quadratic.

transitions_from filters self.transitions in full for one source. Two build-time callers invoke it per state:

  • mark_precedence_decisions (Line 1522) calls is_precedence_decision for every state, and that helper calls transitions_from twice (Lines 1545 and 1552).
  • add_parser_rule_return_edges in crates/antlr-rust-runtime/src/atn/serialized.rs (Line 735) calls it for every state.

Total cost is O(state_count × transition_count) on every parser ATN build and on every ANTLR-metadata deserialization. The comment at Lines 1192-1194 states that duplicate-edge detection was made non-quadratic with transitions_by_source, but this accessor bypasses that index. Reuse the index so lookup is proportional to one state's out-degree.

⚡ Proposed fix to index the lookup by source
     pub fn transitions_from(
         &self,
         source: usize,
     ) -> impl DoubleEndedIterator<Item = ParserTransitionSpec> + '_ {
-        self.transitions
-            .iter()
-            .filter(move |transition| transition.source.index() == source)
-            .map(TransitionBuild::spec)
+        AtnStateId::try_from(source)
+            .ok()
+            .and_then(|id| self.transitions_by_source.get(&id))
+            .map_or(&[][..], Vec::as_slice)
+            .iter()
+            .map(|&index| self.transitions[index].spec())
     }

Note: transitions_by_source records insertion order per source, so next() and next_back() keep the same first/last edge semantics that is_precedence_decision relies on. Call this accessor before finish() sorts self.transitions, which is already the case today.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    pub fn transitions_from(
        &self,
        source: usize,
    ) -> impl DoubleEndedIterator<Item = ParserTransitionSpec> + '_ {
        AtnStateId::try_from(source)
            .ok()
            .and_then(|id| self.transitions_by_source.get(&id))
            .map_or(&[][..], Vec::as_slice)
            .iter()
            .map(|&index| self.transitions[index].spec())
    }

    pub fn finish(mut self) -> Result<ParserAtn, ParserAtnError> {
        self.mark_precedence_decisions();
        self.transitions.sort_by_key(|transition| transition.source);
        let transition_ranges = self.transition_ranges()?;
        self.precompute_state_flags(&transition_ranges);
        let words = self.encode(&transition_ranges)?;
        ParserAtn::from_owned(words)
    }
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/atn/parser_atn.rs` around lines 1350 - 1367,
Update transitions_from to retrieve transitions through the existing
transitions_by_source index instead of scanning self.transitions and filtering
by source. Preserve its DoubleEndedIterator return type and insertion-order
semantics, including next() and next_back(), so mark_precedence_decisions and
add_parser_rule_return_edges retain their current behavior.
crates/antlr-rust-runtime/src/byte_stream.rs (1)

249-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct or remove the disallowed_methods allow attribute.

The comment states that insta assertion macros need this allow, but this test module calls no insta macro. The module does call expect at line 352. Either remove the attribute if the lint does not fire, or restate the reason to match the actual disallowed call.

♻️ Proposed change if the lint does not fire
 #[cfg(test)]
-#[allow(clippy::disallowed_methods)] // insta assertion macros unwrap internal I/O.
 mod tests {

As per coding guidelines: "Every test module or bare test function invoking an insta macro must add #[allow(clippy::disallowed_methods)] because the macros internally unwrap I/O."

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/byte_stream.rs` around lines 249 - 251, Update
the #[allow(clippy::disallowed_methods)] attribute on the tests module to match
actual usage: remove it if clippy does not report a violation, otherwise revise
its comment to explain the module’s expect call rather than referencing
nonexistent insta macros.

Source: Coding guidelines

crates/antlr-rust-runtime/src/char_stream.rs (2)

318-328: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the duplicated clamping in byte_interval.

byte_interval repeats the empty-check and clamping already performed by text_source_interval, and both call byte_bounds. The two code paths must stay in sync, because token text and reported byte spans must agree. The trait default already derives byte_interval from text_source_interval, so the override adds no behavior.

♻️ Proposed refactor
-    fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
-        let len = self.data.len(&self.source);
-        if interval.is_empty() || len == 0 {
-            return None;
-        }
-        let start = interval.start.min(len);
-        let stop = interval.stop.min(len.saturating_sub(1));
-        (start <= stop)
-            .then(|| self.data.byte_bounds(&self.source, start, stop))
-            .flatten()
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.


🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/char_stream.rs` around lines 318 - 328, Remove
the custom byte_interval override and rely on the trait default that derives
byte bounds from text_source_interval. Keep the existing text_source_interval
implementation as the single source for empty-interval handling and index
clamping, ensuring token text and byte spans remain consistent.

351-382: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for Unicode byte bounds.

The tests exercise contiguous_ascii, symbol_at, and position_summary, but no test pins byte_interval or text_source_interval for the InputData::Unicode variant. That path computes byte offsets from byte_offsets and falls back to source.len() for the final scalar. A test over "aβ\nγ" would pin the multi-byte boundary arithmetic.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/char_stream.rs` around lines 351 - 382, Add
assertions to optional_fast_paths_preserve_scalar_indexes_and_positions for the
Unicode InputStream covering byte_interval and text_source_interval on "aβ\nγ",
including an interval ending at the final scalar so the source.len() fallback
and byte_offsets multi-byte boundary calculations are verified.
crates/antlr-rust-runtime/src/dfa.rs (1)

364-376: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Use the change flag that update_sparse already returns instead of two extra edge lookups.

add_edge calls self.edge(source, symbol) before and after self.hot.edges.add(...) to detect whether the edge changed. update_sparse (Line 839) already computes exactly that and returns it as Option<bool>, but add (Line 793) discards the payload with is_some(). Every learned edge therefore pays two extra row walks, and a sparse row walk is O(row length).

Return the change flag from EdgeTable::add and use it directly.

♻️ Proposed refactor to propagate the change flag
     pub(crate) fn add_edge(&mut self, source: DfaStateId, symbol: i32, target: DfaStateId) {
         self.assert_valid_state(source);
         self.assert_valid_state(target);
-        let previous = self.edge(source, symbol);
-        self.hot.edges.add(source, symbol, target);
-        if self.edge(source, symbol) != previous {
+        if self.hot.edges.add(source, symbol, target) {
             self.bump_learning_revision();
         }
     }

EdgeTable::add then returns bool: true for a new slot or a replaced target, false when the stored target already equals target or when slot() rejects the symbol.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/dfa.rs` around lines 364 - 376, Propagate the
existing change result from EdgeTable::update_sparse through EdgeTable::add
instead of discarding it. Update add_edge to use the returned bool directly for
bump_learning_revision, removing both pre- and post-add edge lookups while
preserving false for unchanged targets or rejected symbols.
crates/antlr-rust-runtime/src/errors.rs (1)

36-57: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find constructors and construction sites for SyntaxErrorEvent.
set -euo pipefail

ast-grep run --pattern 'SyntaxErrorEvent { $$$ }' --lang rust crates tests
rg -nP --type=rust -C3 'impl(<[^>]*>)?\s+SyntaxErrorEvent' crates
rg -nP --type=rust -C3 'notify_error_listeners\s*\(' crates tests

Repository: ophi-dev/antlr-rust-runtime

Length of output: 2164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Top-level files:"
git ls-files | sed -n '1,80p'
echo "Cargo files:"
git ls-files '*Cargo.toml'
echo "Target file existence:"
git ls-files | grep 'crates/antlr-rust-runtime/src/errors.rs' || true

Repository: ophi-dev/antlr-rust-runtime

Length of output: 2135


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Top-level entries:"
ls -la
echo "Git status:"
git status --short
echo "Known Rust files mentioning SyntaxErrorEvent:"
rg -n --glob '*.rs' 'SyntaxErrorEvent|notify_error_listeners|TokenSource|Recognizer' . || true

Repository: ophi-dev/antlr-rust-runtime

Length of output: 2135


Provide a public constructor for SyntaxErrorEvent.

SyntaxErrorEvent is #[non_exhaustive] and has no public impl, so external TokenSource implementors or recognizers cannot construct the value required by the public notify_error_listeners API. Add a constructor or builder with the intended diagnostics payload and document how external callers should emit syntax errors.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/errors.rs` around lines 36 - 57, add a public
constructor or builder on SyntaxErrorEvent that accepts all intended diagnostic
fields, including offending, line, column, span, message, and error. Document
its usage for external TokenSource implementations and recognizers invoking the
public notify_error_listeners API, while preserving the #[non_exhaustive] struct
design.
crates/antlr-rust-runtime/src/lib.rs (1)

72-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Re-export TokenSourceError at the crate root.

TokenSourceError appears in the public TokenSource::drain_errors and TokenSource::report_error signatures, and in the public From<&TokenSourceError> for SyntaxErrorEvent conversion. External TokenSource implementors must name the type, but the root re-export list omits it while including TokenStoreError, TokenSink, and TokenSource. Add it for a consistent root surface.

♻️ Proposed change
 pub use token::{
     DEFAULT_CHANNEL, HIDDEN_CHANNEL, INVALID_TOKEN_TYPE, MAX_TOKEN_OFFSET, TOKEN_EOF, Token,
     TokenChannel, TokenId, TokenIter, TokenSink, TokenSource, TokenSpec, TokenStore,
-    TokenStoreError, TokenView,
+    TokenSourceError, TokenStoreError, TokenView,
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

pub use token::{
    DEFAULT_CHANNEL, HIDDEN_CHANNEL, INVALID_TOKEN_TYPE, MAX_TOKEN_OFFSET, TOKEN_EOF, Token,
    TokenChannel, TokenId, TokenIter, TokenSink, TokenSource, TokenSpec, TokenStore,
    TokenSourceError, TokenStoreError, TokenView,
};
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/lib.rs` around lines 72 - 76, Update the
crate-root token re-export list in lib.rs to include TokenSourceError alongside
TokenSource and the other public token symbols, so external users can name the
type exposed by TokenSource and SyntaxErrorEvent APIs.
crates/antlr-rust-runtime/src/prediction.rs (1)

2374-2429: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Pin the upstream DOT dumps with insta snapshots instead of hand-written constants.

pinned_upstream_test_graph_nodes_matches_dot compares generated prediction-context DOT dumps against 38 hand-written const &str values (Lines 1635-1900). The repository guideline requires insta snapshots when a test pins complete generated dumps of this kind. Hand-written constants also make an intentional rendering change expensive to update, because each expected string must be edited by hand.

Keep the explicit assertions that guard the inventory and the selector: CASES.len() == 38, the unique-source-test count, the logical-id count, and the non-empty ANTLR_GRAPH_NODE_CASE selection. Those are inventory and bounds checks, which the guideline retains. Replace only the per-case expected string with insta::assert_snapshot! keyed by case.logical_id, and add #[allow(clippy::disallowed_methods)] to the upstream_graph_nodes module, because the insta macros unwrap internal I/O.

render_dot assigns node ids through the deterministic work_list walk and never emits HashMap iteration order, so the rendered string is stable and safe to snapshot.

Based on learnings from the coding guidelines: "Use insta snapshots instead of hand-written assertions when tests pin complete values, collections, diagnostics, generated code, or token/tree/ATN/DFA dumps" and "Every test module or bare test function invoking an insta macro must add #[allow(clippy::disallowed_methods)]".

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/prediction.rs` around lines 2374 - 2429,
Replace the hand-written per-case expected DOT constants and comparison in
pinned_upstream_test_graph_nodes_matches_dot with insta::assert_snapshot! keyed
by case.logical_id, while preserving the inventory, uniqueness, logical-id, and
selector assertions. Remove only the obsolete expected-string storage and
mismatch aggregation, and add #[allow(clippy::disallowed_methods)] to the
upstream_graph_nodes module.

Source: Coding guidelines

crates/antlr-rust-runtime/src/recognizer.rs (2)

18-23: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not panic on a poisoned error-listener lock.

syntax_error calls expect on the lock. If any registered listener panics once, the mutex stays poisoned, and every later diagnostic panics. Diagnostics run on the error path, so this converts a recoverable syntax error into a panic and hides the original failure. Recover the inner value instead.

🐛 Proposed fix
     fn syntax_error(&self, recognizer: &(dyn Recognizer + '_), event: &SyntaxErrorEvent<'_>) {
         self.0
             .lock()
-            .expect("error listener lock poisoned")
+            .unwrap_or_else(std::sync::PoisonError::into_inner)
             .syntax_error(recognizer, event);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    fn syntax_error(&self, recognizer: &(dyn Recognizer + '_), event: &SyntaxErrorEvent<'_>) {
        self.0
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .syntax_error(recognizer, event);
    }
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/recognizer.rs` around lines 18 - 23, Update the
lock acquisition in the error-listener forwarding method syntax_error to recover
the mutex’s inner listener value when the lock is poisoned instead of calling
expect and panicking. Preserve the existing delegation to self.0’s syntax_error
with the recovered guard for both healthy and poisoned locks.

62-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document that cloned recognizers share listener instances.

ErrorListenerSlot clones an Arc, so cloning RecognizerData produces an independent listener list whose entries point at the same listener objects. The test at lines 369-380 only covers the empty-list case, so this sharing is neither documented nor pinned. Add a doc comment on the error_listeners field or on RecognizerData, and add a test that registers a listener before cloning and asserts that both recognizers record into the same buffer.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/recognizer.rs` around lines 62 - 68, Document
on RecognizerData or its error_listeners field that cloning creates separate
listener lists sharing the same listener instances via Arc. Add a test covering
a registered listener before cloning, then trigger recording through both
recognizers and assert both writes reach the same shared buffer.
crates/antlr-rust-runtime/src/token.rs (2)

384-394: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add dedicated error kinds for a missing source and an inverted byte span.

Line 386 reports a missing source buffer as overflow("source text", 1, 0). The rendered message is "token source text 1 exceeds the supported limit 0", which does not describe the condition. Line 389 reports both an inverted span (start_byte > stop_byte) and an out-of-range span through the same overflow variant, so the inverted case reports an unrelated value and limit pair. A token-source integrator reading these messages receives a wrong explanation.

Add TokenStoreErrorKind::MissingSource and TokenStoreErrorKind::InvalidByteSpan { start, stop, source_len } with matching Display arms, and use them here.

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/token.rs` around lines 384 - 394, Add
TokenStoreErrorKind::MissingSource and TokenStoreErrorKind::InvalidByteSpan {
start, stop, source_len }, including matching Display formatting. In the
source-backed validation flow, replace the overflow error for absent self.source
with MissingSource, and distinguish start_byte > stop_byte by returning
InvalidByteSpan with the span values; retain the existing overflow error for
stop_byte exceeding source.len().

868-894: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Pin the complete token display output with insta snapshots.

Lines 876-879 and 890-893 assert complete rendered token strings. The collected token list at lines 995-1005 pins a complete ordered collection. The guidelines require insta snapshots for tests that pin complete values, collections, or token dumps, and keep explicit assertions for properties, bounds, and ordering invariants. Convert these assertions to assert_snapshot! and assert_debug_snapshot!, and add #[allow(clippy::disallowed_methods)] to the test module.

♻️ Proposed change
 #[cfg(test)]
+#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
 mod tests {
         assert!(!store.view(TokenId(0)).expect("token").is_synthetic());
-        assert_eq!(
-            store.view(TokenId(0)).expect("token").to_string(),
-            "[`@0`,2:4='abc',<7>,3:9]"
-        );
+        insta::assert_snapshot!(
+            "token_view_display_matches_antlr_shape",
+            store.view(TokenId(0)).expect("token").to_string()
+        );

As per coding guidelines: "Use insta snapshots instead of hand-written assertions when tests pin complete values, collections, diagnostics, generated code, or token/tree/ATN/DFA dumps".

🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/token.rs` around lines 868 - 894, Convert the
complete rendered-string assertions in token_view_display_matches_antlr_shape
and synthetic_token_display_uses_antlr_negative_index to insta snapshots, using
assert_snapshot! for display output and assert_debug_snapshot! for the complete
ordered token collection around the collected-list test. Preserve the existing
property assertions for synthetic status and add
#[allow(clippy::disallowed_methods)] to the test module.

Source: Coding guidelines

crates/antlr-rust-runtime/src/tree_pattern.rs (2)

677-682: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not discard tags when TokenId::try_from fails; surface the failure.

filter_map with TokenId::try_from(index).ok()? drops a tag entry when the conversion fails. A dropped entry has a silent, non-local effect: pattern_token_tag and rule_tag_of no longer recognize that leaf, so match_terminals falls back to literal text comparison against the tag's display text, for example <ID>. That text never equals real subject input, so compile returns Ok and the compiled pattern matches nothing, with no diagnostic.

Return a Result and propagate a ParseTreePatternError instead, so a compile that cannot preserve every tag fails loudly. This matches the module's stated policy of failing loudly rather than silently truncating, which tokenize already applies to the EOF suffix.

🐛 Proposed fix
-fn rekey_tags_by_token_id(tags_by_index: &BTreeMap<usize, TagInfo>) -> BTreeMap<TokenId, TagInfo> {
-    tags_by_index
-        .iter()
-        .filter_map(|(&index, tag)| Some((TokenId::try_from(index).ok()?, tag.clone())))
-        .collect()
-}
+fn rekey_tags_by_token_id(
+    tags_by_index: &BTreeMap<usize, TagInfo>,
+) -> Result<BTreeMap<TokenId, TagInfo>, ParseTreePatternError> {
+    tags_by_index
+        .iter()
+        .map(|(&index, tag)| {
+            let id = TokenId::try_from(index).map_err(|_| ParseTreePatternError::Tokenization {
+                message: format!("pattern token index {index} exceeds the token-ID range"),
+            })?;
+            Ok((id, tag.clone()))
+        })
+        .collect()
+}

Update the call site in interpret:

-        let tags = rekey_tags_by_token_id(tags_by_index);
+        let tags = rekey_tags_by_token_id(tags_by_index)?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn rekey_tags_by_token_id(
    tags_by_index: &BTreeMap<usize, TagInfo>,
) -> Result<BTreeMap<TokenId, TagInfo>, ParseTreePatternError> {
    tags_by_index
        .iter()
        .map(|(&index, tag)| {
            let id = TokenId::try_from(index).map_err(|_| ParseTreePatternError::Tokenization {
                message: format!("pattern token index {index} exceeds the token-ID range"),
            })?;
            Ok((id, tag.clone()))
        })
        .collect()
}
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/tree_pattern.rs` around lines 677 - 682, Update
rekey_tags_by_token_id to return a Result<BTreeMap<TokenId, TagInfo>,
ParseTreePatternError> instead of dropping failed TokenId conversions through
filter_map. Convert each index with error propagation, preserving all successful
entries, and update interpret to propagate the returned ParseTreePatternError
through the compile path.

1365-1378: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the stale state-range comment.

The comment says stat = 0..=6, expr = 7..=12. The table below assigns stat states 0..=4 and expr states 5..=10, which set_rule_to_start_state(vec![0, 5]) and set_rule_to_stop_state(vec![4, 10]) confirm. The wrong ranges will mislead the next person who edits this fixture ATN.

📝 Proposed fix
-        // States: stat = 0..=6, expr = 7..=12.
+        // States: stat = 0..=4, expr = 5..=10.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        // States: stat = 0..=4, expr = 5..=10.
        for (number, kind, rule) in [
            (0, AtnStateKind::RuleStart, 0),  // stat start
            (1, AtnStateKind::Basic, 0),      // after ID
            (2, AtnStateKind::Basic, 0),      // after '='
            (3, AtnStateKind::Basic, 0),      // after expr
            (4, AtnStateKind::RuleStop, 0),   // stat stop
            (5, AtnStateKind::RuleStart, 1),  // expr start
            (6, AtnStateKind::BlockStart, 1), // expr decision
            (7, AtnStateKind::Basic, 1),      // INT alt
            (8, AtnStateKind::Basic, 1),      // ID alt
            (9, AtnStateKind::BlockEnd, 1),   // expr block end
            (10, AtnStateKind::RuleStop, 1),  // expr stop
        ] {
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/tree_pattern.rs` around lines 1365 - 1378,
Update the state-range comment above the table in the fixture ATN builder to
match the actual assignments: stat states 0..=4 and expr states 5..=10,
consistent with set_rule_to_start_state and set_rule_to_stop_state.
crates/antlr-rust-runtime/src/tree.rs (1)

1543-1546: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Snapshot the rendered tree dumps instead of asserting them inline.

Lines 1543-1546, 1589-1592, and 1599 pin complete to_string_tree_with_names output with assert_eq!. The repository guidelines require insta snapshots for tests that pin tree dumps, and retain explicit assertions only for properties, bounds, round-trip invariants, and ordering checks. The ordering assertions at Lines 1639 and 1662 stay as they are.

♻️ Proposed change for `stores_rule_children_in_one_pooled_range`
         assert_eq!(parsed.tree().text(), "ab");
         assert_eq!(parsed.tree().children().count(), 2);
         assert_eq!(parsed.storage().stats().edges, 2);
         assert_eq!(parsed.storage().stats().scratch_links, 0);
-        assert_eq!(
-            parsed.tree().to_string_tree_with_names(&["root"]),
-            "(root a b)"
-        );
+        insta::assert_snapshot!(
+            "pooled_children_tree",
+            parsed.tree().to_string_tree_with_names(&["root"])
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        insta::assert_snapshot!(
            "pooled_children_tree",
            parsed.tree().to_string_tree_with_names(&["root"])
        );
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/tree.rs` around lines 1543 - 1546, Replace the
inline to_string_tree_with_names output assertions in
stores_rule_children_in_one_pooled_range with insta snapshots, covering the
dumps currently checked at the referenced assertions. Keep explicit assertions
for properties, bounds, round-trip invariants, and ordering checks, including
the ordering assertions near the end of the test.

Source: Coding guidelines

crates/antlr-rust-runtime/src/xpath.rs (1)

284-298: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report a delimiter lexeme as UnknownPathElement, not InvalidRuleName.

The _ arm treats every lexeme that is not a wildcard, a string, or an upper-case identifier as a rule reference. Anywhere, Root, and Bang reach that arm when they appear in a word position. For the path "///", compile_elements passes the Root lexeme here, and the caller receives InvalidRuleName { name: "/", index: 2 }. Upstream ANTLR reports an unknown path element for that input, and XPathError::UnknownPathElement already exists for exactly this case at Lines 273-278.

Add an explicit guard so only an Identifier resolves as a rule name. Regenerate the upstream_invalid_paths snapshot after the change.

🐛 Proposed fix
     let node_test = match token.kind {
         LexemeKind::Wildcard => NodeTest::Wildcard,
         LexemeKind::String => NodeTest::Token(resolve_token(token, vocabulary)?),
         LexemeKind::Identifier if token.text.starts_with(char::is_uppercase) => {
             NodeTest::Token(resolve_token(token, vocabulary)?)
         }
-        _ => NodeTest::Rule(resolve_rule(token, rule_names)?),
+        LexemeKind::Identifier => NodeTest::Rule(resolve_rule(token, rule_names)?),
+        LexemeKind::Anywhere | LexemeKind::Root | LexemeKind::Bang | LexemeKind::Eof => {
+            return Err(XPathError::UnknownPathElement {
+                element: token.text.clone(),
+                index: token.index,
+            });
+        }
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn compile_element(
    token: &Lexeme,
    axis: Axis,
    invert: bool,
    rule_names: &[String],
    vocabulary: &Vocabulary,
) -> Result<PathElement, XPathError> {
    let node_test = match token.kind {
        LexemeKind::Wildcard => NodeTest::Wildcard,
        LexemeKind::String => NodeTest::Token(resolve_token(token, vocabulary)?),
        LexemeKind::Identifier if token.text.starts_with(char::is_uppercase) => {
            NodeTest::Token(resolve_token(token, vocabulary)?)
        }
        LexemeKind::Identifier => NodeTest::Rule(resolve_rule(token, rule_names)?),
        LexemeKind::Anywhere | LexemeKind::Root | LexemeKind::Bang | LexemeKind::Eof => {
            return Err(XPathError::UnknownPathElement {
                element: token.text.clone(),
                index: token.index,
            });
        }
    };
🤖 Prompt for 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.

In `@crates/antlr-rust-runtime/src/xpath.rs` around lines 284 - 298, Update
compile_element so only LexemeKind::Identifier falls through to resolve_rule;
explicitly return XPathError::UnknownPathElement for delimiter lexemes such as
Anywhere, Root, and Bang encountered in a word position. Preserve the existing
wildcard, string, uppercase-identifier, and valid rule-name handling, then
regenerate the upstream_invalid_paths snapshot.
tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg (2)

178-193: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

self.output() is not reachable inside the listener impls.

LeafListener is a unit struct. Inside visit_terminal and every exit_* callback, self is the listener, not the recognizer, so self.output() has no definition. The design notes state this same risk at Rust.test.stg.design-notes.md lines 296-302, and rust-test-stg-honest-reference-gap.md lines 124-127 repeat it. The gap document also states that the render-then-compile pipeline is now the harness's only pipeline, so these templates must compile.

The same defect exists at lines 225, 233, 256, 264, 285, 293, 315, and 327. Inject the sink into the listener, as Java and C# thread a TextWriter into LeafListener.

🔧 Sketch of an injected sink
-#[derive(Default)]
-struct LeafListener;
+struct LeafListener<'a, W: std::io::Write> {
+    out: &'a mut W,
+}

 impl TListener for LeafListener {
     fn visit_terminal(
         &mut self,
         node: &TerminalNode,
     ) -> Result\<(), std::convert::Infallible> {
-        writeln!(self.output(), "{}", node.symbol().text_or_empty());
+        writeln!(self.out, "{}", node.symbol().text_or_empty());
         Ok(())
     }
 }

WalkListener at lines 195-202 must then construct the listener with the sink.

🤖 Prompt for 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.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg` around lines 178
- 193, Update LeafListener to store an injected output sink instead of calling
self.output(), since listener callbacks cannot access the recognizer. Replace
the affected visit_terminal and exit_* callback output calls to use that sink,
and update WalkListener to construct LeafListener with the sink. Apply this
consistently to every generated listener implementation in the template.

232-237: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Print the token text, not the token.

Line 227 and line 228 use .symbol().text_or_empty(). Line 235 stops at .symbol(), so the {} placeholder formats the token value instead of its text. The upstream Java descriptor prints the token text. Add .text_or_empty().

🐛 Proposed fix
             writeln!(
                 self.output(),
                 "{}",
-                ctx.id_token().expect("ID alternative").symbol()
+                ctx.id_token().expect("ID alternative").symbol().text_or_empty()
             );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            writeln!(
                self.output(),
                "{}",
                ctx.id_token().expect("ID alternative").symbol().text_or_empty()
            );
        }
🤖 Prompt for 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.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg` around lines 232
- 237, Update the token output in the relevant writeln! call within the Rust
test template to invoke text_or_empty() on the result of
ctx.id_token().expect("ID alternative").symbol(), matching the neighboring
token-printing logic and emitting token text rather than the token value.
tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg.design-notes.md (2)

125-210: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The per-template notes contradict the corrections section and the template file.

Section "Reference corrections (post-validation)" at lines 3-49 records the final renderings, but the per-template notes still describe the pre-correction ones. Examples:

  • Line 131: Assert is documented as assert!(<s>). Correction 3 and Rust.test.stg line 56 render it empty.
  • Line 137: Append is documented as <a> + &(<b>).to_string(). Correction 4 and template line 71 render format!.
  • Line 143: the member initializers are documented as let mut <n>: T = <v>;. Correction 2 and template lines 86-88 render <n>: i32 = <v>;.
  • Line 166: RuleInvocationStack is documented as format!("{:?}", …). Correction 6 and template line 150 call java_style_list.
  • Line 182: ParserPropertyMember is documented as fn property(&self). Correction 5 and template line 163 define fn Property.
  • Line 200: TreeNodeWithAltNumField is documented as a MyRuleNode struct. Template line 210 renders empty.
  • Line 204: WalkListener is documented as ParseTreeWalker::walk. Template line 197 calls walk_with_invocation_states.
  • Line 293: Cast is documented as downcast_ref. Template line 66 uses __active_context_view.

Either update these entries, or add one sentence that marks the per-template notes as the pre-correction record, as rust-test-stg-honest-reference-gap.md lines 22-23 do for its own analysis.

🤖 Prompt for 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.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg.design-notes.md`
around lines 125 - 210, Update the conflicting per-template notes in the design
document to match the corrected renderings in the reference corrections section
and Rust.test.stg, including Assert, Append, member initializers,
RuleInvocationStack, ParserPropertyMember, TreeNodeWithAltNumField,
WalkListener, and Cast. Alternatively, clearly label the entire notes section as
a pre-correction record, consistent with rust-test-stg-honest-reference-gap.md.

212-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the markdownlint warnings in this section.

markdownlint-cli2 reports MD022 for the headings at lines 216, 224, 236, 253, 258, and 268, which need a blank line below them. It also reports MD029 for the ordered list items from line 225 onward, because each subsection restarts its own list while continuing the global numbering. The numbering is intentional, since the gap document refers to a "21-capability" checklist. Keep the numbers and either configure MD029 as ordered for this file, or convert the checklist to a single list under one heading.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 216-216: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 224-224: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 225-225: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/2/3

(MD029, ol-prefix)


[warning] 227-227: Ordered list item prefix
Expected: 2; Actual: 4; Style: 1/2/3

(MD029, ol-prefix)


[warning] 229-229: Ordered list item prefix
Expected: 3; Actual: 5; Style: 1/2/3

(MD029, ol-prefix)


[warning] 233-233: Ordered list item prefix
Expected: 4; Actual: 6; Style: 1/2/3

(MD029, ol-prefix)


[warning] 236-236: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 237-237: Ordered list item prefix
Expected: 1; Actual: 7; Style: 1/2/3

(MD029, ol-prefix)


[warning] 239-239: Ordered list item prefix
Expected: 2; Actual: 8; Style: 1/2/3

(MD029, ol-prefix)


[warning] 243-243: Ordered list item prefix
Expected: 3; Actual: 9; Style: 1/2/3

(MD029, ol-prefix)


[warning] 245-245: Ordered list item prefix
Expected: 4; Actual: 10; Style: 1/2/3

(MD029, ol-prefix)


[warning] 247-247: Ordered list item prefix
Expected: 5; Actual: 11; Style: 1/2/3

(MD029, ol-prefix)


[warning] 250-250: Ordered list item prefix
Expected: 6; Actual: 12; Style: 1/2/3

(MD029, ol-prefix)


[warning] 253-253: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 254-254: Ordered list item prefix
Expected: 1; Actual: 13; Style: 1/2/3

(MD029, ol-prefix)


[warning] 256-256: Ordered list item prefix
Expected: 2; Actual: 14; Style: 1/2/3

(MD029, ol-prefix)


[warning] 258-258: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 259-259: Ordered list item prefix
Expected: 1; Actual: 15; Style: 1/2/3

(MD029, ol-prefix)


[warning] 261-261: Ordered list item prefix
Expected: 2; Actual: 16; Style: 1/2/3

(MD029, ol-prefix)


[warning] 263-263: Ordered list item prefix
Expected: 3; Actual: 17; Style: 1/2/3

(MD029, ol-prefix)


[warning] 266-266: Ordered list item prefix
Expected: 4; Actual: 18; Style: 1/2/3

(MD029, ol-prefix)


[warning] 268-268: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 269-269: Ordered list item prefix
Expected: 1; Actual: 19; Style: 1/2/3

(MD029, ol-prefix)


[warning] 270-270: Ordered list item prefix
Expected: 2; Actual: 20; Style: 1/2/3

(MD029, ol-prefix)


[warning] 271-271: Ordered list item prefix
Expected: 3; Actual: 21; Style: 1/2/3

(MD029, ol-prefix)

🤖 Prompt for 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.

In `@tests/antlr-rust-runtime-testsuite/templates/Rust.test.stg.design-notes.md`
around lines 212 - 272, Fix the markdownlint issues in the “Runtime API surface
this assumes” section: add blank lines after the headings identified by MD022,
and preserve the intentional global numbering for the capability checklist while
resolving MD029 by configuring ordered-list numbering for this file or making
the checklist one continuous list under a single heading.

Source: Linters/SAST tools

@tinovyatkin
tinovyatkin merged commit a0ea7cd into main Aug 3, 2026
15 of 16 checks passed
@tinovyatkin
tinovyatkin deleted the refactor/virtual-cargo-workspace branch August 3, 2026 20:52
tinovyatkin added a commit that referenced this pull request Aug 8, 2026
The CodegenLiteralAccessor probe in interp_test.rs sits inside the
phase-C locked oracle section, so switching its expectations from
literal `pub fn` accessor signatures to `__antlr4_rust_context_accessors!`
declaration lines changed the section hash that every phase-C evidence
ledger pins. Record the change the sanctioned way: approve the rewrite
as a normalization in refactorSectionsEquivalent (mirroring the
CodegenData precedent) and regenerate the ledgers with
generate-port-evidence --update, which re-verifies the pinned test and
implementation commits modulo the approved normalizations.

Regeneration itself was blocked by a pre-existing break: pinned commits
that predate the virtual-workspace-root move (#291) store runtime
sources at src/, but gitShowOptional only consulted historicalPath,
which has no antlr-rust-runtime remap, so the empty-vocabulary check
aborted the run even on main (CI only executes the validator, which
tolerates missing history, so this went unnoticed). Resolve pinned
sources by trying the current path, the historical remap, and the
pre-move runtime location in order.
tinovyatkin added a commit that referenced this pull request Aug 8, 2026
…#326)

* feat(codegen): emit context accessors from one declaration per method

Every generated context's child accessors were emitted twice: once for
the recovery-oriented states (generic over `__RecoveryContextState`,
returning `Result<_, MissingChildError>` for required children) and once
for the validated state (`ValidatedTreeContext`, returning required
children directly behind an `unreachable!` guard). The pairs differed
only in required-child unwrapping and the validated-child constructor,
making up roughly 23% of generated parser source and leaving room for
variant-drift bugs.

Extend the #276 technique to the accessor surface: a new runtime-owned
`__antlr4_rust_context_accessors!` macro expands one declarative record
per accessor into both state-variant impls. Generated source now
declares each accessor once, e.g.

    antlr4_runtime::__antlr4_rust_context_accessors! {
        KeyValueContext {
            rule key: required(KeyContext[4], "key"),
            token equals_token: required(8, "EQUALS"),
        }
    }

with `rule`/`token`/`label_rule`/`label_token` kinds covering every
existing shape: required/optional/many cardinalities, `nth`/`last_after`
label selectors, `skip` for list labels, and single-type vs token-set
labeled-token sources (`__labeled_token_children` vs `_matching`). All
grammar data (names, indices, token types) appears only in the generated
invocation, keeping the macro grammar-agnostic. Signatures, behavior,
`MissingChildError` diagnostics, and the validated `unreachable!`
message text are preserved; the antlr4rust-compat wrapper impls and
`validate_tree_structure` arms remain generator-rendered since they have
no state-variant duplication.

Newly generated source requires the new macro, so the generated-code API
revision increments to 8; revisions 1-7 stay accepted because the
runtime still provides everything their generated source needs. All
checked-in recognizers are regenerated and the compatibility test,
snapshots, and docs/migration.md (previously stale at revision 6) are
updated.

Generated parser source shrinks accordingly (lines/bytes):

  toml_parser.rs      3884 -> 3123   198169 -> 163702  (-19.6%/-17.4%)
  antlr_v4_parser.rs  9858 -> 7595   573366 -> 466022  (-23.0%/-18.7%)
  rust_parser.rs     37087 -> 30975 2609384 -> 2291910 (-16.5%/-12.2%)

Verified: workspace clippy/tests clean, upstream conformance sweep 357/357,
grammar-frontend Stage 0/1/2 fixed point, and rustdoc for a generated
crate still documents the macro-expanded public accessors.

Closes #323

* fix(codegen): restore accessor-surface assertions and add macro catch-alls

Address review feedback on the declarative accessor macro:

- The antlr4rust_unrelated_context_surface snapshot filtered for `pub fn`
  lines, which the declaration format no longer produces, leaving an
  empty snapshot that could not catch a dropped accessor. Widen the
  filter to also keep rule/token/label_rule/label_token declaration
  lines so the snapshot again pins UnrelatedContext's full surface.
- The compat surface test lost its `self__token` assertion when the
  paired impls collapsed into declarations; assert its declaration
  alongside r#type and self_.
- Give __antlr4_rust_context_accessors! catch-all arms that emit a
  compile_error! naming the context and the offending declaration,
  selector, or token set, following the __antlr4_rust_require_codegen_api!
  precedent, instead of failing with a bare "no rules expected this
  token" deep inside a #[rustfmt::skip] generated module.
- Document the __from_child_node/__from_validated_child_node
  constructors and the __node/__invocation_states fields in the macro's
  generator-contract doc comment.

* test(codegen): re-lock phase-C evidence for declaration-based accessors

The CodegenLiteralAccessor probe in interp_test.rs sits inside the
phase-C locked oracle section, so switching its expectations from
literal `pub fn` accessor signatures to `__antlr4_rust_context_accessors!`
declaration lines changed the section hash that every phase-C evidence
ledger pins. Record the change the sanctioned way: approve the rewrite
as a normalization in refactorSectionsEquivalent (mirroring the
CodegenData precedent) and regenerate the ledgers with
generate-port-evidence --update, which re-verifies the pinned test and
implementation commits modulo the approved normalizations.

Regeneration itself was blocked by a pre-existing break: pinned commits
that predate the virtual-workspace-root move (#291) store runtime
sources at src/, but gitShowOptional only consulted historicalPath,
which has no antlr-rust-runtime remap, so the empty-vocabulary check
aborted the run even on main (CI only executes the validator, which
tolerates missing history, so this went unnoticed). Resolve pinned
sources by trying the current path, the historical remap, and the
pre-move runtime location in order.

* test(codegen): pin native/compat coexistence and compile every macro arm

Address the second review pass:

- The antlr4rust_compat_generated_surface snapshot needles matched only
  the compat wrapper methods after accessors became declarations; add
  the rule/token declaration needles so the snapshot again pins that the
  native fallible surface coexists with the compat getters, matching the
  unrelated-context snapshot fix.
- Extend the validated-tree fixture with a `tails += COMMA*` list token
  label so the `label_token many(skip(n), [...])` arms — previously the
  only accessor shape never compiled by the workspace test run — are
  generated, compiled, and exercised at runtime in both the populated
  and empty cases.
- Report catch-all macro diagnostics in source declaration order
  (`rule broken: bogus(...)`) via structured catch-all arms, keeping the
  fully generic arms for shapes that do not parse as a declaration.
- Comment the intentionally unbalanced brace handling in the
  generated_parser_api accessor-declaration scan.
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.

1 participant