Repository navigation
parse-bench: investigate Rust vs Go AST divergence on C# Mono fixtures #109
Description
Summary
When running parse-bench with --ast-check, several C# fixtures produce byte-different parse trees between rust-antlr and go-antlr, even though both sides complete without error nodes. Kotlin, Java, and Trino already match under the same gate.
This blocks enabling --ast-check for the full default language set and makes Rust-vs-Go throughput numbers on those fixtures unfair (different trees ⇒ different work).
Affected fixtures
Under tools/parse-bench/fixtures/csharp/:
| Fixture | Status |
|---|---|
dotnet-wpf-datagrid-column.cs |
AST parity ok |
mono-codegen.cs |
diverges |
mono-anonymous.cs |
diverges |
mono-statement.cs |
diverges |
Observed symptom
Example from mono-codegen.cs (Rust has more children under using_directives than Go):
Rule(compilation_unit, children=3)
- Rule(using_directives, children=9)
+ Rule(using_directives, children=6)Rust includes additional using directives (and later additional statements/members) that Go’s tree omits. Diffs also show extra using IKVM... / related regions on the Rust side.
Neither dump contains Err(...) nodes — both parses “succeed”, they just build different CSTs.
Likely cause (hypothesis)
C# grammars-v4 ships target-specific support bases:
- Go:
csharp/v7/Go/CSharpLexerBase.go,CSharpParserBase.go - Python: similar
- Rust: generated from
.interp(+ whatever preprocessor/channel handling the runtime/lexer path implements)
The Mono fixtures use preprocessor conditionals (#if / #endif and related). Go’s CSharpLexerBase implements preprocessor directive handling; the Rust path may be:
- not skipping the same
#ifregions Go skips, or - treating hidden/preprocessor channels differently, or
- diverging on some other support-base behavior (not necessarily pure ATN prediction).
This needs confirmation with a minimal #if repro and side-by-side token streams / tree dumps.
How to reproduce
# Setup: ANTLR 4.13.2 jar + grammars-v4 sparse checkout including csharp/v7
# (see AGENTS.md / tools/parse-bench/README.md)
python3 tools/parse-bench/run.py --languages csharp --runtimes rust-antlr,go-antlr --ast-check --quick --work-dir target/parse-bench-csharp-astDumps land under:
target/parse-bench-csharp-ast/ast-dumps/csharp/*.rust.txt
target/parse-bench-csharp-ast/ast-dumps/csharp/*.go.txt
Investigation plan
- Minimal repro — strip a Mono fixture to the smallest
#if/usingpattern that still diverges. - Token-stream parity — dump default-channel (and hidden) tokens from Rust vs Go for that repro; check whether Go drops preprocessor-disabled regions that Rust keeps.
- Support-base gap — compare
CSharpLexerBase(Go) preprocessor mode/#ifevaluation with Rust lexer generation / channel handling for the same grammar. - Decide the fix surface:
- Rust lexer/runtime channel + preprocessor support for C#, or
- checked-in Rust support module analogous to Go/Python bases, or
- fixture normalization (only if the divergence is fixture-specific and not a real product gap).
- Once fixed, extend CI
--ast-checklanguages to includecsharp.
Acceptance criteria
- Root cause documented (token stream vs parse decision vs support base).
-
mono-*.csfixtures pass--ast-checkagainst Go (byte-identical dumps, no error nodes), or fixtures are explicitly justified as non-comparable with a tracked exception. - CI AST parity step can include
csharpwithout special-casing.
Related
- parse-bench
--ast-check(Rust/Go tree dump gate) - Recent fix for Java
precpred/>vs>>left-recursive loop-enter fast path (unrelated mechanism; C# looks preprocessor/support-base shaped)
Activity
Add parse-bench AST parity gate and fix Java > vs >> precpred (#110)