Browse Source

Generate typed Slots; key 1:1-kind child access on them

The generator emits a Slots holder of typed CSharpSlotInfo<T> kind constants (T is
the child type, widened to AstNode for the few kinds reused with several child
types). Call sites whose kind maps to a single type switch from
GetChildByRole<T>(SlotKind.X) to GetChild(Slots.X), inferring the result type from
the slot. The ByRole methods and SlotKind remain for the multi-type Attribute
accessor and the internal add/insert plumbing, removed in following steps.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3807/head
Siegfried Pammer 2 weeks ago committed by Siegfried Pammer
parent
commit
b23499503c
  1. 33
      ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
  2. 18
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 10
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs
  6. 4
      ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs
  7. 7
      ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs
  8. 2
      ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs
  9. 12
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs
  10. 2
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs
  11. 4
      ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs
  12. 2
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  13. 2
      ICSharpCode.Decompiler/Output/TextTokenWriter.cs

33
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

@ -781,13 +781,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -781,13 +781,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
// it replaces the old polymorphic node.Role == Roles.X comparisons.
void WriteSlotKinds(SourceProductionContext context, ImmutableArray<AstNodeAdditions> source)
{
var kinds = new SortedSet<string>(StringComparer.Ordinal);
// Per kind: the element types seen (to choose a typed slot's T) and whether it is a collection.
// kindIsCollection is null once a kind is seen as a collection on one node and a single child on
// another (e.g. Expression, Initializer); it is resolved by agreement, so the emitted flag never
// depends on iteration order (each kind's value is written exactly once it is determined).
var kindTypes = new SortedDictionary<string, SortedSet<string>>(StringComparer.Ordinal);
var kindIsCollection = new Dictionary<string, bool?>();
foreach (var node in source)
{
if (node.Slots is { } slots)
{
foreach (var s in slots)
kinds.Add(s.KindName);
{
if (!kindTypes.TryGetValue(s.KindName, out var set))
kindTypes[s.KindName] = set = new SortedSet<string>(StringComparer.Ordinal);
set.Add(s.ElementType);
if (!kindIsCollection.TryGetValue(s.KindName, out var arity))
kindIsCollection[s.KindName] = s.IsCollection;
else if (arity is bool b && b != s.IsCollection)
kindIsCollection[s.KindName] = null;
}
}
}
@ -800,9 +813,23 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -800,9 +813,23 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
builder.AppendLine("public enum SlotKind");
builder.AppendLine("{");
builder.AppendLine("\tNone,");
foreach (var k in kinds)
foreach (var k in kindTypes.Keys)
builder.AppendLine($"\t{k},");
builder.AppendLine("}");
builder.AppendLine();
// Typed kind constants for polymorphic access: node.GetChild(Slots.X) infers the child type. A
// kind reused with several child types across node types widens to AstNode (only its concrete
// per-node slots carry the precise type); such kinds are only reached through those per-node slots.
builder.AppendLine("/// <summary>Typed slot kinds for polymorphic child access; the child type is inferred from the slot.</summary>");
builder.AppendLine("public static class Slots");
builder.AppendLine("{");
foreach (var kv in kindTypes)
{
string type = kv.Value.Count == 1 ? kv.Value.Min : "AstNode";
string isColl = kindIsCollection[kv.Key] == true ? "true" : "false";
builder.AppendLine($"\tpublic static readonly CSharpSlotInfo<{type}> {kv.Key} = new CSharpSlotInfo<{type}>(\"{kv.Key}\", {isColl}, SlotKind.{kv.Key}, false);");
}
builder.AppendLine("}");
context.AddSource("SlotKind.g.cs", SourceText.From(builder.ToString().Replace("\r\n", "\n"), Encoding.UTF8));
}

18
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -1355,11 +1355,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1355,11 +1355,11 @@ namespace ICSharpCode.Decompiler.CSharp
RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
if (propDef.Getter != null)
{
RemoveAttribute(prop.GetChildByRole<Accessor>(SlotKind.Getter)!, KnownAttribute.ExtensionMarker);
RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker);
}
if (propDef.Setter != null)
{
RemoveAttribute(prop.GetChildByRole<Accessor>(SlotKind.Setter)!, KnownAttribute.ExtensionMarker);
RemoveAttribute(prop.GetChild(Slots.Setter)!, KnownAttribute.ExtensionMarker);
}
RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(propDef.DeclaringTypeDefinition));
break;
@ -1412,7 +1412,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1412,7 +1412,7 @@ namespace ICSharpCode.Decompiler.CSharp
EntityDeclaration memberDecl, IMethod method,
TypeSystemAstBuilder astBuilder)
{
if (memberDecl.GetChildByRole<AstType>(SlotKind.PrivateImplementationType) is not null)
if (memberDecl.GetChild(Slots.PrivateImplementationType) is not null)
{
yield break; // cannot create forwarder for existing explicit interface impl
}
@ -1439,9 +1439,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1439,9 +1439,9 @@ namespace ICSharpCode.Decompiler.CSharp
methodDecl.ReturnType = memberReturnType.Clone();
methodDecl.PrivateImplementationType = astBuilder.ConvertType(m.DeclaringType);
methodDecl.Name = m.Name;
methodDecl.TypeParameters.AddRange(memberDecl.GetChildrenByRole<TypeParameterDeclaration>(SlotKind.TypeParameter)
methodDecl.TypeParameters.AddRange(memberDecl.GetChildren(Slots.TypeParameter)
.Select(n => (TypeParameterDeclaration)n.Clone()));
methodDecl.Parameters.AddRange(memberDecl.GetChildrenByRole<ParameterDeclaration>(SlotKind.Parameter).Select(n => n.Clone()));
methodDecl.Parameters.AddRange(memberDecl.GetChildren(Slots.Parameter).Select(n => n.Clone()));
// Constraints are not copied because explicit interface implementations cannot have constraints. CS0460
methodDecl.Body = new BlockStatement();
@ -1557,7 +1557,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1557,7 +1557,7 @@ namespace ICSharpCode.Decompiler.CSharp
void FixParameterNames(EntityDeclaration entity)
{
int i = 0;
foreach (var parameter in entity.GetChildrenByRole<ParameterDeclaration>(SlotKind.Parameter))
foreach (var parameter in entity.GetChildren(Slots.Parameter))
{
if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList())
{
@ -1854,11 +1854,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1854,11 +1854,11 @@ namespace ICSharpCode.Decompiler.CSharp
RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
if (p.Getter != null)
{
RemoveAttribute(prop.GetChildByRole<Accessor>(SlotKind.Getter)!, KnownAttribute.ExtensionMarker);
RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker);
}
if (p.Setter != null)
{
RemoveAttribute(prop.GetChildByRole<Accessor>(SlotKind.Setter)!, KnownAttribute.ExtensionMarker);
RemoveAttribute(prop.GetChild(Slots.Setter)!, KnownAttribute.ExtensionMarker);
}
extMemberDecl = prop;
break;
@ -2136,7 +2136,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2136,7 +2136,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
int i = parameterOffset;
var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index!.Value);
foreach (var parameter in entityDecl.GetChildrenByRole<ParameterDeclaration>(SlotKind.Parameter))
foreach (var parameter in entityDecl.GetChildren(Slots.Parameter))
{
if (parameters.TryGetValue(i, out var v))
parameter.AddAnnotation(new ILVariableResolveResult(v, method.Parameters[i].Type));

10
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs

@ -123,7 +123,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -123,7 +123,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if ((ConversionFlags & ConversionFlags.PlaceReturnTypeAfterParameterList) != ConversionFlags.PlaceReturnTypeAfterParameterList
&& (ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType)
{
var rt = node.GetChildByRole<AstType>(SlotKind.Type);
var rt = node.GetChild(Slots.Type);
if (rt is not null)
{
rt.AcceptVisitor(new CSharpOutputVisitor(writer, formattingPolicy));
@ -150,7 +150,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -150,7 +150,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
else
{
parameters = node.GetChildrenByRole<ParameterDeclaration>(SlotKind.Parameter);
parameters = node.GetChildren(Slots.Parameter);
}
foreach (var param in parameters)
{
@ -181,7 +181,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -181,7 +181,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
if ((ConversionFlags & ConversionFlags.PlaceReturnTypeAfterParameterList) == ConversionFlags.PlaceReturnTypeAfterParameterList
&& (ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType)
{
var rt = node.GetChildByRole<AstType>(SlotKind.Type);
var rt = node.GetChild(Slots.Type);
if (rt is not null)
{
writer.Space();
@ -309,7 +309,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -309,7 +309,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
else
{
writer.WriteIdentifier(node.NameToken);
WriteTypeParameters(node.GetChildrenByRole<TypeParameterDeclaration>(SlotKind.TypeParameter), writer, formattingPolicy);
WriteTypeParameters(node.GetChildren(Slots.TypeParameter), writer, formattingPolicy);
}
}
@ -412,7 +412,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -412,7 +412,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
writer.WriteIdentifier(Identifier.Create(name));
break;
}
WriteTypeParameters(node.GetChildrenByRole<TypeParameterDeclaration>(SlotKind.TypeParameter), writer, formattingPolicy);
WriteTypeParameters(node.GetChildren(Slots.TypeParameter), writer, formattingPolicy);
}
void WriteTypeParameters(IEnumerable<TypeParameterDeclaration> typeParameters, TokenWriter writer, CSharpFormattingOptions formattingPolicy)

2
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1904,7 +1904,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1904,7 +1904,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitLabelStatement(LabelStatement labelStatement)
{
StartNode(labelStatement);
WriteIdentifier(labelStatement.GetChildByRole<Identifier>(SlotKind.Identifier)!);
WriteIdentifier(labelStatement.GetChild(Slots.Identifier)!);
WriteToken(Roles.Colon);
bool foundLabelledStatement = false;
for (AstNode? tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling)

2
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs

@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public IdentifierExpression(string identifier, TextLocation location)
{
SetChildByRole(SlotKind.Identifier, Decompiler.CSharp.Syntax.Identifier.Create(identifier, location));
SetChild(Slots.Identifier, Decompiler.CSharp.Syntax.Identifier.Create(identifier, location));
}
[Slot("Identifier")]

4
ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs

@ -36,10 +36,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -36,10 +36,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public string AttributeTarget {
get {
return GetChildByRole<Identifier>(SlotKind.Identifier)?.Name ?? string.Empty;
return GetChild(Slots.Identifier)?.Name ?? string.Empty;
}
set {
SetChildByRole(SlotKind.Identifier, Identifier.Create(value));
SetChild(Slots.Identifier, Identifier.Create(value));
}
}

7
ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs

@ -46,9 +46,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching @@ -46,9 +46,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching
var ident = other as IdentifierExpression;
if (ident == null || ident.TypeArguments.Any())
return false;
if (match.Get(referencedGroupName).Last() is not IdentifierExpression referenced)
if (match.Get(referencedGroupName).Last() is not AstNode referenced)
return false;
return ident.Identifier == referenced.Identifier;
Identifier? referencedIdentifier = referenced.GetChild(Slots.Identifier);
if (referencedIdentifier == null)
return false;
return ident.Identifier == referencedIdentifier.Name;
}
}
}

2
ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs

@ -43,7 +43,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -43,7 +43,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public SimpleType(string identifier, TextLocation location)
{
SetChildByRole(SlotKind.Identifier, Syntax.Identifier.Create(identifier, location));
SetChild(Slots.Identifier, Syntax.Identifier.Create(identifier, location));
}
[Slot("Identifier")]
public partial string? Identifier { get; set; }

12
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs

@ -42,21 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -42,21 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public virtual string Name {
get {
return GetChildByRole<Identifier>(SlotKind.Identifier)?.Name ?? string.Empty;
return GetChild(Slots.Identifier)?.Name ?? string.Empty;
}
set {
SetChildByRole(SlotKind.Identifier, Identifier.Create(value, TextLocation.Empty));
SetChild(Slots.Identifier, Identifier.Create(value, TextLocation.Empty));
}
}
public virtual Identifier NameToken {
get { return GetChildByRole<Identifier>(SlotKind.Identifier)!; }
set { SetChildByRole(SlotKind.Identifier, value); }
get { return GetChild(Slots.Identifier)!; }
set { SetChild(Slots.Identifier, value); }
}
public virtual AstType ReturnType {
get { return GetChildByRole<AstType>(SlotKind.Type)!; }
set { SetChildByRole(SlotKind.Type, value); }
get { return GetChild(Slots.Type)!; }
set { SetChild(Slots.Type, value); }
}
protected bool MatchAttributesAndModifiers(EntityDeclaration o, PatternMatching.Match match)

2
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs

@ -72,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -72,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public bool IsExtensionMethod {
get {
ParameterDeclaration? pd = GetChildByRole<ParameterDeclaration>(SlotKind.Parameter);
ParameterDeclaration? pd = GetChild(Slots.Parameter);
return pd != null && pd.HasThisModifier;
}
}

4
ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs

@ -42,7 +42,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -42,7 +42,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
foreach (var typeDecl in rootNode.DescendantsAndSelf.OfType<TypeDeclaration>())
{
var memberNames = typeDecl.Members.Select(m => {
var type = m.GetChildByRole<AstType>(SlotKind.PrivateImplementationType);
var type = m.GetChild(Slots.PrivateImplementationType);
return type is null ? m.Name : type + "." + m.Name;
}).ToHashSet();
// memberNames does not include fields or non-custom events because those
@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (symbol != null && renamedSymbols.TryGetValue(symbol, out string? newName))
{
// An IdentifierExpression / MemberReferenceExpression always carries its name identifier.
node.GetChildByRole<Identifier>(SlotKind.Identifier)!.Name = newName;
node.GetChild(Slots.Identifier)!.Name = newName;
}
}
}

2
ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -482,7 +482,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -482,7 +482,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
var ci = new ConstructorInitializer { ConstructorInitializerType = type };
// Move arguments from invocation to initializer:
invocation.GetChildrenByRole<Expression>(SlotKind.Argument).MoveTo(ci.Arguments);
invocation.GetChildren(Slots.Argument).MoveTo(ci.Arguments);
// Add the initializer: (unless it is the default 'base()')
if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0))
constructorDeclaration.Initializer = ci.CopyAnnotationsFrom(invocation);

2
ICSharpCode.Decompiler/Output/TextTokenWriter.cs

@ -302,7 +302,7 @@ namespace ICSharpCode.Decompiler @@ -302,7 +302,7 @@ namespace ICSharpCode.Decompiler
// Attach member reference to token only if there's no identifier in the current node.
var member = GetCurrentMemberReference();
var node = nodeStack.Peek();
if (member != null && node.GetChildByRole<Identifier>(SlotKind.Identifier) is null)
if (member != null && node.GetChild(Slots.Identifier) is null)
{
switch (member)
{

Loading…
Cancel
Save