The C# syntax tree's name accessors (ParameterDeclaration.Name, GotoStatement.Label,
catch variable, query continuations, ...) are convenience strings over a backing Identifier
token. Names that may be absent are now typed string? and read as null when absent, instead of
a non-null empty-string sentinel; required names stay non-null string. The backing token slot's
nullability follows, and an empty or null assignment clears the token (empty == absent).
Because the property type now carries optionality, the separate [NameSlot] attribute and its
nullOnEmpty flag are redundant: a [Slot] on a string property is a name (child slots are
AstNode-typed, so the type disambiguates), and the generator infers optionality from the
declared nullable annotation -- which a string? declaration already requires #nullable for. The
repeated empty-to-null setter body becomes Identifier.CreateIfNotEmpty. To make the annotation
readable, #nullable enable is turned on across the syntax node files (the directive the inferred
optionality depends on), with the attendant local nullability fixups. Consumers that feed a name
into a non-null slot assert it where the variable structurally has a name.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Nodes use nullable reference types now, so the generated null-object
node and its hasNullNode / NeedsNullNode / NullNodeBaseCtorParamCount
plumbing are dead. Drop them and reword the affected comments.
The 124 concrete node classes that are neither a base of another node nor a
host for a generated PatternPlaceholder subclass are now sealed, so the JIT can
devirtualize the slot dispatch (GetChild, GetChildCount, GetChildSlotInfo,
GetChildNodes, AcceptVisitor) when the static type is the sealed leaf. Identifier's
constructor becomes private (protected is meaningless and an error in a sealed
type). Abstract bases, base classes (PreProcessorDirective, the EntityDeclaration/
AstType/Statement/Expression hierarchy roots), and pattern-placeholder hosts stay
open.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The [Slot] and [NameSlot] string argument only feeds SlotKind derivation, which
takes the last dotted segment and strips a trailing "Role", so the Roles.
qualifier and the Role suffix were dead decoration. Rewrite each to the bare
SlotKind it already produces, so the attribute reads as exactly the kind.
Collapse the runs of consecutive blank lines that removing the hand-written
constructors, fields and properties left between the remaining members.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Generate DoMatch across the remaining expression, statement, type-member,
type-reference and general-scope nodes, including the inherited
EntityDeclaration name/return-type/attribute match, and stop generating it for
abstract base nodes. Matching every structural member fixes real
under-matching bugs (PointerReferenceExpression ignored its Target;
ExtensionDeclaration matched anything) while computed or derived members are
excluded from matching. PrimitiveExpression and PreProcessorDirective stay
hand-written by design. Pretty output is byte-identical.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The grammar-production doc comments were transcribed in ANTLR style and
often copied the C# spec verbatim, including sub-rules the AST does not
model. Rewrite them as W3C EBNF (::=, ?, *, +), unroll sub-productions
that are not themselves AST nodes (e.g. anonymous_function_modifier ->
'async'), and shape each production to the node's actual members. Render
top-level alternations of node productions as multi-line <code> blocks;
keep operator and keyword token lists inline.
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
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.