Skip to content

feat(codegen): hoist the generated support preamble into the runtime - #329

Merged
tinovyatkin merged 1 commit into
mainfrom
codegen/hoist-generated-support-preamble
Aug 9, 2026
Merged

feat(codegen): hoist the generated support preamble into the runtime#329
tinovyatkin merged 1 commit into
mainfrom
codegen/hoist-generated-support-preamble

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #318.

What

The 284-line grammar-independent support preamble that every generated parser re-declared (__GeneratedInput through impl __RecoveryContextState for __ActiveParserContext {}) now lives once in antlr4_runtime::generated as real #[doc(hidden)] items (public generated API excepted), and generated modules import it:

  • pub use antlr4_runtime::generated::{ErrorNode, StoredTreeContext, TerminalNode, __GeneratedInput, __GeneratedTokenView}; keeps the previously-public items reachable under their current generated paths.
  • A plain use brings the module-internal helpers (__GeneratedRuleContext, child-iteration helpers, __FromActiveRuleContext + view adapters, __write_invocation_states, __RecoveryContextState) into scope so the runtime-owned __antlr4_rust_context! / __antlr4_rust_context_accessors! expansions — which resolve these names unhygienically at the invocation site — keep working unchanged for both old and new generated source.
  • The hoisted helpers carry #[inline] so downstream recognizers keep the cross-crate inlining they previously got from module-local definitions.
  • No wildcard prelude; imports stay explicit and fully qualified. Nothing grammar-specific moved into the runtime.

Triage dedup

Node::terminal_view / Node::labeled_terminal_view (crate-private, tree.rs) are now the single home of the terminal-child triage. They back RuleNodeView::{child_tokens, terminal_children}, ParserRuleContext::{child_tokens, terminal_children, labeled_terminal_children}, and the hoisted __terminal_children / __labeled_token_children* helpers. The generated copy and its "Keep this triage aligned" comment are gone.

What deliberately did not move

ValidatedTreeContext stays emitted per generated module. The accessors macro emits overlapping impl blocks for State: __RecoveryContextState and for ValidatedTreeContext; rustc accepts them only while it can prove the validated marker never implements the trait, and that negative reasoning requires the marker type to be local to the generated crate (moving it upstream fails with E0592 "upstream crates may add a new impl of trait __RecoveryContextState for type ValidatedTreeContext"). Documented on the macro and on the emitted marker.

Compatibility

  • __ANTLR4_RUST_CODEGEN_API 8 → 9; arms 1–8 remain accepted (old generated source declares its own preamble and needs no removed runtime surface). Diagnostic, compat test, snapshots, README, and docs/migration.md updated.
  • TerminalNode/ErrorNode gained a hidden node() accessor because the emitted Visitable impls previously read the module-local private __node field, which a runtime-owned type no longer permits.
  • All three checked-in recognizers regenerated (toml + rust via update-generated.sh --update, g4 via the update-stage0.sh two-stage bootstrap, which converged and passed the pinned corpus).

Measurements (acceptance criteria)

Generated source, per parser: −272 lines / −7,014 bytes

parser lines before → after bytes before → after
toml_parser.rs 3,123 → 2,851 163,702 → 156,688
antlr_v4_parser.rs 7,595 → 7,323 466,022 → 459,008
rust_parser.rs 30,975 → 30,703 2,291,910 → 2,284,896

Binary size (antlr4-rust-gen, which links all three generated parsers; default release profile, no LTO, x86-64 Linux): unstripped 43,281,296 → 43,298,288 (+0.04%); stripped 35,117,720 → 35,131,464 (+0.04%) — size-neutral. Symbol-level diff confirms the per-parser helper instantiations (e.g. three __context_children FromFn instances) collapse to one runtime instance; the small residual is scattered codegen-unit drift. Without the #[inline] hints the delta was +47 KB, so the hints are kept.

Testing

check result
cargo fmt --check (runtime + codegen) clean
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings clean
cargo test --locked --workspace --all-features all green (896 lib + 93 CLI integration + 382 runtime + rest)
tools/toml-syntax/update-generated.sh --check current
tools/rust-syntax/update-generated.sh --check current
tools/grammar-frontend/update-stage0.sh --check self-hosting fixed point holds
ANTLR runtime testsuite sweep 357 passed, 0 failed, 0 skipped

Note for reviewers: the coherence constraint above is why ValidatedTreeContext appears in generated output while its former neighbors don't — that split is intentional, not an oversight.

Summary by CodeRabbit

  • New Features

    • Generated parsers now use shared runtime support for tokens, parse-tree nodes, contexts, and traversal.
    • Added validated-tree support for terminal and error nodes.
    • Input facade support is now included only when required by the grammar.
  • Compatibility

    • Updated the code-generation API to revision 9.
    • Runtime compatibility remains available for generated parsers using revisions 1–8.

…to the runtime

Every generated parser module carried a 284-line grammar-independent support
preamble (__GeneratedInput/__GeneratedTokenView, the TerminalNode/ErrorNode
wrappers, the __GeneratedRuleContext source enum with its child-iteration
helpers, the __FromActiveRuleContext trait and view adapters,
__write_invocation_states, and the __RecoveryContextState markers) that was
byte-identical across every generated parser, so N linked parsers compiled N
copies of the same code and the __terminal_children triage had to be kept
aligned with the runtime by hand.

These items now live once in antlr4_runtime::generated as real #[doc(hidden)]
(public API items excepted) definitions, and generated modules import them by
name instead of re-declaring them: `pub use` for the items that were public
generated API (TerminalNode, ErrorNode, StoredTreeContext, __GeneratedInput,
__GeneratedTokenView) so existing paths keep resolving, and a plain `use` for
the module-internal helpers the runtime-owned context macros resolve
unhygienically at the invocation site. The helpers carry #[inline] so
downstream recognizers keep the cross-crate inlining they previously got from
module-local definitions.

Two deliberate non-moves:

- ValidatedTreeContext stays emitted per generated module. The accessors
  macro emits overlapping impl blocks for `State: __RecoveryContextState` and
  for `ValidatedTreeContext`, and rustc only accepts them while it can prove
  the validated marker never implements the trait - which requires the marker
  type to be local to the generated crate (E0592 "upstream crates may add a
  new impl" otherwise). This constraint is documented on the macro and on the
  emitted marker.
- The terminal-child triage now exists exactly once: Node::terminal_view /
  Node::labeled_terminal_view in tree.rs back RuleNodeView::terminal_children,
  ParserRuleContext::terminal_children/child_tokens/labeled_terminal_children,
  and the hoisted generated helpers, so the hand-aligned copy (and its
  alignment comment) is gone.

The generated-source contract changes, so __ANTLR4_RUST_CODEGEN_API is now 9;
revisions 1-8 remain accepted because their generated source declares its own
preamble and needs no removed runtime surface. TerminalNode/ErrorNode gained a
hidden node() accessor because the emitted Visitable impls previously read the
module-local private __node field, which a runtime-owned type no longer
permits.

Measured on the checked-in recognizers: each generated parser shrinks by 272
lines / 7,014 bytes (toml 3123 -> 2851, g4 7595 -> 7323, rust 30975 -> 30703;
-816 lines total). The antlr4-rust-gen binary, which links all three parsers,
is size-neutral (stripped: 35,117,720 -> 35,131,464 bytes, +0.04%) under the
default no-LTO release profile.

Closes #318
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

Copy/Paste Detection

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

Show duplications

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

  • Starting at line 4213 of crates/antlr-rust-codegen/src/generator/tests.rs
  • Starting at line 4436 of crates/antlr-rust-codegen/src/generator/tests.rs
    atn.set_end_state(1, 4).expect("block end state");
    atn.add_transition(0, ParserTransitionSpec::Epsilon { target: 1 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 2 })
        .expect("transition");
    atn.add_transition(1, ParserTransitionSpec::Epsilon { target: 3 })
        .expect("transition");
    atn.add_transition(
        2,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 1,
        },
    )
    .expect("transition");
    atn.add_transition(
        3,
        ParserTransitionSpec::Atom {
            target: 4,
            label: 2,
        },
    )
    .expect("transition");
    atn.add_transition(4, ParserTransitionSpec::Epsilon { target: 5 })
        .expect("transition");
    atn.add_decision_state(1).expect("decision state");
```rust

---

Found a 26 line (125 tokens) duplication in the following files:
* Starting at line 741 of crates/antlr-rust-runtime/src/generated.rs
* Starting at line 912 of crates/antlr-rust-runtime/src/generated.rs

```rust
            $input: $crate::char_stream::CharStream,
            $hooks: $crate::parser::SemanticHooks,
        {
            pub fn metadata() -> &'static $crate::generated::GrammarMetadata {
                $metadata()
            }

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

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

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

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

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

---

Found a 22 line (112 tokens) duplication in the following files:
* Starting at line 4311 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 4385 of crates/antlr-rust-codegen/src/generator/tests.rs

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

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

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

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

---

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

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

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

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

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

---

Found a 28 line (102 tokens) duplication in the following files:
* Starting at line 3331 of crates/antlr-rust-codegen/src/generator/tests.rs
* Starting at line 3491 of crates/antlr-rust-codegen/src/generator/tests.rs

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

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

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

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

---

Found a 20 line (100 tokens) duplication in the following files:
* Starting at line 15 of crates/antlr-rust-codegen/src/parser/surface/names.rs
* Starting at line 343 of crates/antlr-rust-codegen/src/parser/surface/support_abi.rs

```rust
    for (rule_index, rule) in model.rules.iter().enumerate() {
        if !rule.has_attrs() {
            continue;
        }
        let struct_name = embedded::attrs_struct_name(rule_index);
        let mut fields = String::new();
        for attr in &rule.attrs {
            let _ = writeln!(
                fields,
                "    pub {}: {},",
                embedded::escape_keyword(&attr.name),
                attr.ty
            );
        }
        let _ = writeln!(
            out.attrs_structs,
            "#[derive(Clone, Debug, Default)]\n#[allow(non_snake_case, dead_code)]\npub struct {struct_name} {{\n{fields}}}\n"
        );
    }
    out.module_items.push_str(&render_embedded_context_types(

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1d54d251-1eb4-4136-9445-7cabbb99da2b

📥 Commits

Reviewing files that changed from the base of the PR and between 8c26871 and 914d110.

⛔ Files ignored due to path filters (17)
  • crates/antlr-rust-codegen/src/generator/snapshots/antlr_rust_codegen__generator__tests__generated_module_file_header.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_checks.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__cli__generated_codegen_api_mismatch_diagnostic.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__parser__inlined_token_accessors_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__configured_mutual_entry_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__configured_precedence_entry_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__inferred_mutual_entries_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__pruned_unreachable_rule_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__recursive_eof_entry_component_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/snapshots/antlr4_rust_gen_cli__transforms__unreachable_rule_default_generated_api.snap is excluded by !**/*.snap
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-g4-parser/src/generated/antlr_v4_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-rs-parser/src/generated/rust_parser.rs is excluded by !**/generated/**
  • crates/antlr-rust-toml-parser/src/generated/toml_lexer.rs is excluded by !**/generated/**
  • crates/antlr-rust-toml-parser/src/generated/toml_parser.rs is excluded by !**/generated/**
  • docs/migration.md is excluded by !**/docs/**
📒 Files selected for processing (14)
  • README.md
  • crates/antlr-rust-codegen/src/generator/tests.rs
  • crates/antlr-rust-codegen/src/parser/render/mod.rs
  • crates/antlr-rust-codegen/src/parser/surface/contexts.rs
  • crates/antlr-rust-codegen/src/parser/surface/facade.rs
  • crates/antlr-rust-codegen/src/parser/surface/names.rs
  • crates/antlr-rust-codegen/src/parser/surface/support_abi.rs
  • crates/antlr-rust-codegen/src/parser/surface/traversal.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs
  • crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/semantics.rs
  • crates/antlr-rust-runtime/src/generated.rs
  • crates/antlr-rust-runtime/src/lib.rs
  • crates/antlr-rust-runtime/src/tree.rs
  • third_party/antlr-v4-grammar/self-hosted.sha256
💤 Files with no reviewable changes (2)
  • crates/antlr-rust-codegen/src/parser/surface/facade.rs
  • crates/antlr-rust-codegen/src/parser/surface/names.rs

📝 Walkthrough

Walkthrough

The runtime now owns shared generated parser input, tree, context, and traversal support. The generator imports this support instead of emitting duplicate declarations. The codegen API revision advances to 9, with updated compatibility tests and documentation.

Changes

Runtime-owned parser support

Layer / File(s) Summary
Add runtime generated support
crates/antlr-rust-runtime/src/generated.rs
Adds shared input, token, node, context, iterator, active-context, invocation-state, and recovery-state APIs.
Centralize terminal traversal
crates/antlr-rust-runtime/src/tree.rs
Adds shared terminal conversion helpers and updates child and labeled-token iterators to use them.
Update generated parser surface
crates/antlr-rust-codegen/src/parser/..., crates/antlr-rust-codegen/src/generator/tests.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/semantics.rs
Removes duplicate generated support, updates runtime imports, conditionally emits the input facade, and emits validated tree support.
Revise compatibility contract
crates/antlr-rust-runtime/src/lib.rs, crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/cli.rs, README.md, third_party/antlr-v4-grammar/self-hosted.sha256
Advances the API revision to 9 and updates compatibility coverage, documentation, and generated-file checksums.

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

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedParser
  participant RuntimeGenerated
  participant RuntimeTree
  GeneratedParser->>RuntimeGenerated: import shared parser support
  RuntimeGenerated->>RuntimeTree: convert terminal and error nodes
  RuntimeTree-->>GeneratedParser: return tree and child views
Loading

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive Core hoisting, API revision, re-exports, and traversal changes are evidenced; excluded snapshots and generated recognizers prevent full verification. Verify the excluded snapshot files and generated recognizers, which are filtered by !/*.snap and !/generated/**.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: moving generated support code into the runtime.
Out of Scope Changes check ✅ Passed The changes align with issue #318, including runtime hoisting, tree traversal centralization, compatibility updates, tests, documentation, and generated artifacts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codegen/hoist-generated-support-preamble

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

❤️ Share

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

@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown

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


Code Review: hoist the generated support preamble into the runtime

Reviewed the full diff against origin/main, read the pre-image of every hoisted item, and verified locally.

Verification run on this branch

check result
cargo build --locked --workspace --all-features exit 0
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings exit 0
cargo test --locked --workspace --all-features exit 0, 0 failures
tools/toml-syntax/update-generated.sh --check "the checked-in TOML recognizer is current"
tools/rust-syntax/update-generated.sh --check "the checked-in Rust recognizer is current"

The hoist itself is semantically clean. I diffed each moved item against its removed generated counterpart: __context_children, __rule_children, __terminal_children, __token_children{,_matching}, __labeled_token_children{,_matching}, __write_invocation_states, __FromActiveRuleContext, __active_context_view{,_with_attrs}, StoredTreeContext / __ActiveParserContext / __RecoveryContextState, and the TerminalNode/ErrorNode wrappers are all behaviour-preserving, including the subtle synthetic-only triage in the labeled variant. Elided lifetimes (__GeneratedRuleContext<'_>impl Iterator<Item = …<'_>> + '_) are unambiguous everywhere. The tree.rs dedup preserves RuleNodeView::{child_tokens, terminal_children} and ParserRuleContext::{child_tokens, terminal_children, labeled_terminal_children} exactly.

Three things I checked specifically and found correct:

  • Macro hygiene / rev-8 compat. Both macro bodies are unchanged (doc comments only), so bare identifiers still resolve at the invocation site — rev ≤ 8 modules find their own preamble, rev 9 modules find the imports. Nothing was removed from the runtime, so retaining arms 1–8 satisfies the CLAUDE.md rule.
  • Nothing references the deleted __labeled_token_child anywhere in the repo, and Rust.test.stg only reaches for __ActiveParserContext and __active_context_view — both in the emitted import list.
  • The ValidatedTreeContext exception is right. With the trait upstream and the marker local, the generated crate's negative reasoning holds; move the marker upstream and it can't, because a future runtime could add the impl. The reviewer note in the description is worth keeping in the commit message.

A nice unadvertised side effect: because each generated file ends in pub use self::__antlr4_rust_generated::*;, two generated parsers in one crate now re-export the same TerminalNode item, so the glob collision that previously errored resolves silently.

Findings below are all non-blocking — test-quality, docs, and hardening. No correctness defect found.


1. Two assertions in attrless_contexts_skip_generated_attrs_lookup are now vacuous

crates/antlr-rust-codegen/src/generator/tests.rs:1924-1930

assert!(
    attributed.contains("__active_context_view,"),
    "native embedded actions must import the runtime active-context helper"
);
assert!(
    attributed.contains("__active_context_view_with_attrs,"),
    "compatibility lowering must import the runtime live-attribute helper"
);

Both match substrings of the fixed import list in render/mod.rs:252, which is emitted for every parser: build_parser_surface_model (render_model.rs:366) always returns either embedded or structural, so the else { "" } arm at render/mod.rs:254 is unreachable. Confirmed against real output — the checked-in antlr_v4_parser.rs is a structural (non-embedded) render and contains both substrings. Neither assertion can fail for any grammar, and the messages now claim something importing a name cannot establish.

To be fair: the pre-image assertions (T::__from_active(context, None, …) / Some(live_attrs)) were also matching unconditional preamble bodies, so this is a preserved weakness rather than a regression. But the rewrite is the natural moment to make them real — assert on the emitted call site instead, which is grammar-dependent: embedded/translate.rs:1149 emits __active_context_view_with_attrs::<{Ctx}<'_, __ActiveParserContext>>( only for attributed contexts, so attributed.contains("__active_context_view_with_attrs::<") paired with a negative on attrless actually discriminates.

Fix this →

2. The *_generated_api.snap snapshots stopped pinning the re-exported public API

crates/antlr-rust-codegen/tests/antlr4_rust_gen_cli/support.rs:306

generated_parser_api scrapes source text for pub const fn , pub const , pub enum , pub fn , pub static , pub struct , pub trait , pub type — there is no pub use arm. So struct TerminalNode, struct ErrorNode, struct StoredTreeContext, struct __GeneratedInput, struct __GeneratedTokenView plus fn symbol, fn is_error, fn is_missing, fn la, fn lt dropped out of all seven API snapshots rather than moving to a re-export entry.

Failure scenario: someone later trims a name out of the pub use list in render/mod.rs:252 — say ErrorNode, which no checked-in recognizer's public surface happens to name. Every *_generated_api.snap stays green, and downstream code referencing parser::ErrorNode breaks. Adding a ("pub use ", …) arm that records the braced names restores the guard and re-lists the ten entries that vanished here.

Fix this →

3. ~350 lines moved into the runtime with no direct runtime tests

crates/antlr-rust-runtime/src/generated.rs:1635

mod tests still only covers GrammarMetadata (plus the facade_hygiene compile-only fixture, which exercises the lexer/parser facade macros, not the new items). Nothing in the runtime exercises TerminalNode::{symbol, is_error, is_missing, node}, ErrorNode, __write_invocation_states, or the __*_children triage — coverage is entirely transitive through the checked-in recognizers and the conformance sweep. The runtime now owns this API, so it should test it: __write_invocation_states in particular has a trivially pinnable contract ([13 6], [] for the empty chain), and the terminal/error-node triage over a small recovered tree is exactly the "snapshot the whole structure" case CLAUDE.md calls out.

4. docs/migration.md doesn't mention that these types became foreign

TerminalNode, ErrorNode, and StoredTreeContext moved from the user's own crate into antlr4_runtime. Any downstream impl ForeignTrait for TerminalNode<'_>serde::Serialize is the realistic one, and a hand-written Debug now also collides with the new derive — stops compiling under the orphan rule, and regenerating does not fix it. The migration note currently only says "regenerate"; one sentence naming this would save a confusing E0117/E0119. Same paragraph could note the new Debug derives.

5. __RecoveryContextState is now publicly implementable

crates/antlr-rust-runtime/src/generated.rs:1629

The trait was module-private before, so nothing outside a generated module could implement it. It is now #[doc(hidden)] pub and unsealed, which means a downstream crate can write impl antlr4_runtime::generated::__RecoveryContextState for ValidatedTreeContext {} against its own generated marker and immediately break the very coherence the doc comment above it depends on (E0592 on the two accessor impl blocks). Sealing it — private supertrait in a private module — removes the footgun while leaving rustc's negative reasoning about the local marker intact.

Nits

  • __GeneratedTokenView::text (generated.rs:1344) was made pub, but nothing outside the runtime constructs the type — __GeneratedInput::lt is the only constructor, and the text() accessor already exists. (__GeneratedInput.0 genuinely does need pub, for surface/contexts.rs:268.)
  • Display for TerminalNode / for ErrorNode (generated.rs:1401, :1443) are the only non-generic hoisted fns without #[inline]; cheap consistency with the stated rationale.
  • __Antlr4RustInput / __Antlr4RustTokenView (surface/facade.rs:25-66) are equally grammar-independent and still emitted per module — a natural follow-up to the same issue.
  • Generated output now carries three consecutive blank lines where the preamble stood (harmless, the module is #[rustfmt::skip]).
  • TerminalNode / ErrorNode are new non-doc-hidden runtime API whose methods carry no doc comments, and neither cross-references the existing TerminalNodeView / ErrorNodeView. Two similarly-named public terminal wrappers in one crate is worth a See also line.

The API-revision bump follows the CLAUDE.md checklist completely: macro arms, diagnostic string, the generated_modules_enforce_codegen_api_compatibility loop widened to [1..=8], both codegen-api snapshots, all three checked-in recognizers (with the g4 self-hosting hashes), README, and docs/migration.md. The README also quietly corrects a pre-existing staleness — it claimed revision 7 while main emitted 8.
· branch codegen/hoist-generated-support-preamble

@codspeed-hq

codspeed-hq Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing codegen/hoist-generated-support-preamble (914d110) with main (8c26871)

Open in CodSpeed

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.11299% with 106 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/antlr-rust-runtime/src/generated.rs 32.45% 102 Missing ⚠️
crates/antlr-rust-runtime/src/tree.rs 77.77% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

📊 Source Code Metrics (this PR vs main)

File Cyclomatic Cognitive Functions LLOC MI
crates/antlr-rust-runtime/src/tree.rs 329 (main: 339) 🟢 84 (main: 92) 🟢 178 (main: 176) 🔴 475 (main: 473) 🔴 0 ⚪
crates/antlr-rust-runtime/src/generated.rs 75 (main: 20) 🔴 8 (main: 0) 🔴 40 (main: 14) 🔴 61 (main: 21) 🔴 0 (main: 2.30) 🔴
crates/antlr-rust-codegen/src/parser/surface/support_abi.rs 157 ⚪ 95 ⚪ 30 ⚪ 251 (main: 252) 🟢 0 ⚪
crates/antlr-rust-codegen/src/parser/surface/names.rs 65 ⚪ 40 ⚪ 16 ⚪ 108 (main: 109) 🟢 5.08 (main: 5.04) 🟢
crates/antlr-rust-codegen/src/parser/surface/contexts.rs 25 ⚪ 22 ⚪ 4 ⚪ 69 (main: 72) 🟢 16.57 (main: 11.54) 🟢
crates/antlr-rust-codegen/src/parser/render/mod.rs 43 ⚪ 41 ⚪ 3 ⚪ 92 ⚪ 6.99 (main: 6.92) 🟢
crates/antlr-rust-codegen/src/parser/surface/traversal.rs 6 ⚪ 2 ⚪ 3 ⚪ 36 ⚪ 15.68 (main: 14.92) 🟢
crates/antlr-rust-codegen/src/parser/surface/facade.rs 6 ⚪ 1 ⚪ 1 ⚪ 11 ⚪ 38.82 (main: 34.15) 🟢
crates/antlr-rust-runtime/src/lib.rs 4 ⚪ 3 ⚪ 1 ⚪ 7 ⚪ 29.51 (main: 29.59) 🔴

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

@tinovyatkin
tinovyatkin merged commit d3b0af8 into main Aug 9, 2026
14 of 15 checks passed
@tinovyatkin
tinovyatkin deleted the codegen/hoist-generated-support-preamble branch August 9, 2026 19:36
@ophiarch ophiarch Bot mentioned this pull request Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegen: hoist the byte-identical generated support preamble into the runtime

1 participant