From 603d43a8d95d4f98f30ef26ccf1a56034fdd3aac Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 20 Jun 2026 12:21:40 +0200 Subject: [PATCH] Carry the child type on CSharpSlotInfo Add a generic CSharpSlotInfo (T is the child type, the element type for a collection) and have the generator emit each per-node slot field as the typed variant. This lets the typed child accessors infer the result type from the slot, so the explicit type argument can drop out of the SlotKind-based child API in the following steps. No behavior change -- the typed slots are still consumed through the non-generic CSharpSlotInfo base. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../DecompilerSyntaxTreeGenerator.cs | 5 +++-- .../CSharp/Syntax/CSharpSlotInfo.cs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index c389bdfba..5b7d792ab 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -608,9 +608,10 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator builder.AppendLine("\t}"); builder.AppendLine(); - // One CSharpSlotInfo static per slot; node.Slot compares against these by object identity. + // One typed CSharpSlotInfo static per slot; node.Slot compares against these by object + // identity, and the typed child accessors infer the child type from the slot. foreach (var s in slots) - builder.AppendLine($"\tpublic static readonly CSharpSlotInfo {s.PropertyName}Slot = new CSharpSlotInfo(\"{s.PropertyName}\", typeof({s.ElementType}), {(s.IsCollection ? "true" : "false")}, SlotKind.{s.KindName}, {(s.IsCollection || s.IsNullable ? "true" : "false")});"); + builder.AppendLine($"\tpublic static readonly CSharpSlotInfo<{s.ElementType}> {s.PropertyName}Slot = new CSharpSlotInfo<{s.ElementType}>(\"{s.PropertyName}\", {(s.IsCollection ? "true" : "false")}, SlotKind.{s.KindName}, {(s.IsCollection || s.IsNullable ? "true" : "false")});"); builder.AppendLine(); builder.AppendLine("\tinternal override CSharpSlotInfo GetChildSlotInfo(int index)"); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs index ae6bc0a03..355714f8f 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax /// its parent, so node.Slot == X.YSlot identifies a node's position by object identity (the /// successor to node.Role == X.YRole). /// - public sealed class CSharpSlotInfo + public class CSharpSlotInfo { /// The slot's property name, e.g. "Left", "Body", "Parameters". public string Name { get; } @@ -64,4 +64,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public override string ToString() => Name; } + + /// + /// A that carries its child type as a type parameter, so the typed + /// child accessors (node.GetChild(SomeNode.XSlot)) infer the result type from the slot + /// instead of needing an explicit type argument. is the child type (the + /// element type for a collection slot). + /// + public sealed class CSharpSlotInfo : CSharpSlotInfo where T : AstNode + { + internal CSharpSlotInfo(string name, bool isCollection, SlotKind kind, bool isOptional) + : base(name, typeof(T), isCollection, kind, isOptional) + { + } + } }