A declaration's attribute collection (AstNodeCollection<AttributeSection>) and an
attribute section's own attributes (AstNodeCollection<Attribute>) both carried
[Slot("Attribute")], so the kind mapped to two child types and the shared
Slots.Attribute widened to AstNode. Give the declaration-level slot its own
"AttributeSection" kind so every kind maps to a single type; EntityDeclaration's
Attributes accessor now reads through the typed Slots.AttributeSection. This was
the last child-access keyed on the SlotKind matching enum.
Assisted-by: Claude:claude-opus-4-8:Claude Code
A blank line as the first line inside a node class body (right after the opening brace) or the
last line before the closing brace, left over from the migration. Cosmetic only -- deletions of blank
lines at class boundaries; method bodies are untouched.
Assisted-by: Claude:claude-opus-4-8:Claude Code
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
NodeType was NRefactory's coarse node category, but only three reads remained
here: two checks now expressed as "is not Trivia" and one debug assert for the
pattern category. Remove the enum, the abstract property, every per-node
override, and the generator's emission, preserving the pattern-placeholder case
through an IPatternPlaceholder marker interface the output-visitor assert
checks. Also remove the unused PrimitiveExpression.AdvanceLocation helper.
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
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.