Slot identity already lived in node.Slot/CSharpSlotInfo/SlotKind after
the storage flip, so the parallel Role/Role<T> child model was redundant.
The Role-keyed mutation/query API is reexpressed over SlotKind, and the
role-index packing on AstNode flags is gone.
Because a node's Slot is now derived from its index in its parent rather
than stored on the child, the located-AST reattach in
InsertMissingTokensDecorator must capture the child's slot kind before
Remove() detaches it.
Assisted-by: Claude:claude-opus-4-8:Claude Code
With every optional slot nullable, the null-object pattern is dead. Generated
non-nullable getters return the backing field directly, which surfaced a last
tier of slots the decompiler legitimately leaves empty (omitted range operands,
an implicitly-typed array creation, unnamed parameters, an unbound generic
argument, and others) and flips them to nullable too. The machinery is then
removed entirely: the per-node null classes, the .Null statics and
VisitNullNode, AstNode.IsNull, the role null object, and Identifier.Null.
AcceptVisitor becomes unconditionally generated, and consumers move from
.IsNull to is null and from unconditional visits to ?.AcceptVisitor.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Turn on #nullable enable across the AST transform pipeline, ahead of
annotating the slot properties themselves. TransformContext now exposes the
nullable CurrentMember/CurrentTypeDefinition/CurrentModule contract already
declared by ITypeResolveContext, and the generated pattern-to-node conversion
returns a non-null node so patterns can be used in collection initializers
without warnings. No IL changes.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The pattern matcher walked collections through INode.Role/FirstChild/
NextSibling, skipping siblings of a different role. Now that each
AstNodeCollection<T> is already the per-role child list, the engine matches two
collections by list index, and INode sheds Role/FirstChild/NextSibling
entirely. A collection exposes its IReadOnlyList<INode> view through a cached
adapter rather than implementing the interface directly, so a typed collection
does not become ambiguous for LINQ. Characterization tests pin the matcher's
behavior first.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Children were kept in a per-node doubly-linked list with the slot accessors
layered over it as a view. Storage now is the slot model: each node stores its
children in generated backing fields, AstNodeCollection<T> is backed by a
List<T>, and the flattened child-index space is owned by generated
GetChildCount/GetChild/SetChild/GetChildSlot members, with sibling navigation,
the role API and Clone re-expressed over them and indices renumbered lazily. A
DEBUG CheckInvariant runs after each transform, the analog of the IL
pipeline's per-transform check, so a transform that corrupts the tree fails at
that transform. Output is unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code