Replace the hand-written DoMatch overrides on the expression nodes with
generated ones. The generator matches every real child and structural member,
so several matchers become stricter than the hand-written versions that
under-matched (for example a previously-skipped child or an ignored flag),
while Pretty output is unaffected. Convenience-string identifier slots are
excluded so the name string is matched once rather than also via its token.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Each concrete syntax node now carries, in an XML-doc <remarks> block, the
matching production from the C# language specification grammar (ECMA/Microsoft,
ANTLR notation), quoted verbatim. Aggregate nodes (e.g. BinaryOperatorExpression,
ComposedType, TypeDeclaration) list every production they span; lexical/trivia
nodes (Comment, the preprocessor directives, Identifier) cite the lexical rule.
Nodes with no spec production -- ErrorExpression, UndocumentedExpression,
InvocationAstType, TypeReferenceExpression, NamedExpression, DocumentationReference,
and the C# 14 ExtensionDeclaration -- carry a hand-written EBNF plus a note
explaining why no official production exists.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Comments and preprocessor directives were positional children interleaved
into the child list, and punctuation, keywords and operators were token-node
children. Add a leading/trailing trivia side-channel for comments and
directives, emit it from the output visitor, and re-home every comment
receiver onto it (including inside-block comments as comment-only empty
statements and undecodable attribute arguments as an ErrorExpression). With
locations and sequence points no longer sourced from token nodes, stop
reconstructing them on the locations path and delete CSharpTokenNode,
CSharpModifierToken and InsertSpecialsDecorator. The AST no longer carries
token children or positional comments; output is byte-identical.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Each child of a C# AST node is declared as a [Slot] partial property, and the
source generator emits the accessor bodies and an ordered slot schema
(SlotCount/GetSlotRole/IsCollectionSlot) from them. Generating the schema
keeps slot order from being mis-stated by hand and lets a DEBUG invariant
check declared slot order against document order on every decompile. The node
hierarchy is converted family by family; the EntityDeclaration leaves flatten
their inherited Attributes/ReturnType/NameToken into each leaf's ordered slot
set. Storage stays the NRefactory linked list at this stage, so only the
declaration model changes and output is unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The generator emits the IAstVisitor interface, the AcceptVisitor overloads,
and the null-node and pattern-placeholder nodes from [DecompilerAstNode]
declarations, so drop the hand-written equivalents across the C# AST: per-node
AcceptVisitor/DoMatch, the #region Null / #region PatternPlaceholder blocks,
IAstVisitor.cs, and now-dead usings. Also adds AccessorKind and moves
IdentifierExpressionBackreference into the PatternMatching folder.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Introduce a Roslyn source generator that emits the visitor boilerplate for
the C# AST from [DecompilerAstNode]-tagged node declarations: the
IAstVisitor interface, the AcceptVisitor overloads, the pattern-placeholder
nodes, and the initial DoMatch support. AccessorKind lets an accessor's
keyword be chosen independently of its role, an early step toward shedding
the NRefactory role model.
The C# AST inherited NRefactory's freezable model (IFreezable, Freeze,
IsFrozen, a frozen flag bit, and ThrowIfFrozen guards on every mutator),
but the decompiler never uses it: nothing calls Freeze(), not even the
generated null-node singletons, so every IsFrozen guard only ever
evaluated false. The decompiler is single-threaded and never shares or
freezes nodes. Remove the whole apparatus as preparation for the
slot-based AST rewrite, which has no place for it. Roles are untouched
here, so the flags word keeps its role index; only the freed frozen bit
goes away.