Repository navigation
Conversation
C# requires `partial` to be the last modifier immediately before the
type keyword, so `ref partial struct S { }` is the only legal order —
the reverse, `partial ref struct S { }`, is a compiler error (CS1585).
`_struct_declaration_initializer` only recognized a bare `optional('ref')`
after the modifier repeat, so the legal order shredded into an ERROR
node while the illegal order happened to parse (`partial` fell inside
`repeat($.modifier)` and `ref` still matched the trailing optional).
This broke real-world code: MessagePack-CSharp's core reader/writer
types are declared `public ref partial struct MessagePackReader` /
`internal ref partial struct SequenceReader<T>`, and the ERROR
previously swallowed the struct's entire ~1,100-line body.
Fixes tree-sitter#361
Change `optional('ref')` to `optional(seq('ref', optional('partial')))`
in `_struct_declaration_initializer`. `ref` in every other position
(returns, parameters, locals, lambda parameters) is untouched — it's
produced by unrelated rules and the existing corpus tests for all of
those still pass unmodified.
Regenerated with tree-sitter-cli 0.26.5, matching package-lock.json's
pin. Added `ref partial struct` corpus cases (bare, with an access
modifier, and with a type parameter) to test/corpus/structs.txt,
alongside the existing `ref struct` case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
C# requires
partialto be the last modifier immediately before the type keyword, soref partial struct S { }is the only legal order for a partial ref struct. The reverse,partial ref struct S { }, is a compiler error (CS1585) — noted in #361 (comment by @sharpchen, 2026-03-21)._struct_declaration_initializeronly recognized a bareoptional('ref')sitting afterrepeat($.modifier):Since
partialis a$.modifier, that ordering has it backwards: the C#-illegalpartial ref structparses fine (partialis absorbed byrepeat($.modifier), thenrefstill matches the trailingoptional('ref')), while the only legal order,ref partial struct, shreds into anERRORnode —refis consumed, but the parser has nowhere to put thepartialthat follows before hittingstruct.Minimal repro
public ref partial struct Reader { }(master, HEAD9150f7d5, tree-sitter-cli 0.26.5):Same input on this branch:
Real-world casualty
MessagePack-CSharp's core reader/writer types use exactly this construct:
Parsing
MessagePackReader.cson master produces a singleERRORnode spanning file rows[21,0]-[1117,5]— the struct's entire ~1,100-line body. On this branch that's gone; the file parses cleanly (the only remaining diagnostics are two unrelated 1-lineERRORs at bare#else/#endifdirective tokens from an unconnected preprocessor-conditional-modifier gap that reproduces identically on a plainclass— not something this PR touches or introduces).The fix
One rule, one line, in
_struct_declaration_initializer:refin every other grammatical position — return types, parameters (ref/ref readonly/scoped ref), locals, lambda parameters — is produced by unrelated rules and is untouched; the existing corpus suite covers all of those (e.g.test/corpus/expressions.txt's "Ref local declaration and ref expression" and "Lambda expression with ref modifier",test/corpus/statements.txt's "Function conditional ref expression",test/corpus/type-methods.txt'sref/scoped ref/ref readonlyparameter cases) and all still pass unmodified.Regenerated
src/grammar.json/src/parser.c(andsrc/tree_sitter/array.h, which drifts along with any regen under this exact pinned CLI version, same as commit f2dea45) withtree-sitter-cli 0.26.5— matchingpackage-lock.json's pin exactly.Tests
Added three cases to
test/corpus/structs.txt's existing "Struct with a type parameter struct constraint" test, alongside the file's one pre-existingref struct Test { }case: a bareref partial struct,public ref partial struct MessagePackReader(the real-world construct verbatim), andinternal ref partial struct SequenceReader<T>(real-world construct plus a type parameter). Full suite: 179/179 corpus tests pass on both master and this branch (same total — this extends an existing test rather than adding a new title), zero new failures;npm testandnpm run lintboth clean.Fixes #361
🤖 Generated with Claude Code