diff --git a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs index 5b7d792ab..c4d0ee781 100644 --- a/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs +++ b/ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs @@ -781,13 +781,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax // it replaces the old polymorphic node.Role == Roles.X comparisons. void WriteSlotKinds(SourceProductionContext context, ImmutableArray source) { - var kinds = new SortedSet(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>(StringComparer.Ordinal); + var kindIsCollection = new Dictionary(); 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(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 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("/// Typed slot kinds for polymorphic child access; the child type is inferred from the slot."); + 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)); } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 2a592dd9d..798f5c022 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1355,11 +1355,11 @@ namespace ICSharpCode.Decompiler.CSharp RemoveAttribute(prop, KnownAttribute.ExtensionMarker); if (propDef.Getter != null) { - RemoveAttribute(prop.GetChildByRole(SlotKind.Getter)!, KnownAttribute.ExtensionMarker); + RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker); } if (propDef.Setter != null) { - RemoveAttribute(prop.GetChildByRole(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 EntityDeclaration memberDecl, IMethod method, TypeSystemAstBuilder astBuilder) { - if (memberDecl.GetChildByRole(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 methodDecl.ReturnType = memberReturnType.Clone(); methodDecl.PrivateImplementationType = astBuilder.ConvertType(m.DeclaringType); methodDecl.Name = m.Name; - methodDecl.TypeParameters.AddRange(memberDecl.GetChildrenByRole(SlotKind.TypeParameter) + methodDecl.TypeParameters.AddRange(memberDecl.GetChildren(Slots.TypeParameter) .Select(n => (TypeParameterDeclaration)n.Clone())); - methodDecl.Parameters.AddRange(memberDecl.GetChildrenByRole(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 void FixParameterNames(EntityDeclaration entity) { int i = 0; - foreach (var parameter in entity.GetChildrenByRole(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 RemoveAttribute(prop, KnownAttribute.ExtensionMarker); if (p.Getter != null) { - RemoveAttribute(prop.GetChildByRole(SlotKind.Getter)!, KnownAttribute.ExtensionMarker); + RemoveAttribute(prop.GetChild(Slots.Getter)!, KnownAttribute.ExtensionMarker); } if (p.Setter != null) { - RemoveAttribute(prop.GetChildByRole(SlotKind.Setter)!, KnownAttribute.ExtensionMarker); + RemoveAttribute(prop.GetChild(Slots.Setter)!, KnownAttribute.ExtensionMarker); } extMemberDecl = prop; break; @@ -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(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)); diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs index 98fd45331..9268961b9 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs @@ -123,7 +123,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor if ((ConversionFlags & ConversionFlags.PlaceReturnTypeAfterParameterList) != ConversionFlags.PlaceReturnTypeAfterParameterList && (ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) { - var rt = node.GetChildByRole(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 } else { - parameters = node.GetChildrenByRole(SlotKind.Parameter); + parameters = node.GetChildren(Slots.Parameter); } foreach (var param in parameters) { @@ -181,7 +181,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor if ((ConversionFlags & ConversionFlags.PlaceReturnTypeAfterParameterList) == ConversionFlags.PlaceReturnTypeAfterParameterList && (ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) { - var rt = node.GetChildByRole(SlotKind.Type); + var rt = node.GetChild(Slots.Type); if (rt is not null) { writer.Space(); @@ -309,7 +309,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor else { writer.WriteIdentifier(node.NameToken); - WriteTypeParameters(node.GetChildrenByRole(SlotKind.TypeParameter), writer, formattingPolicy); + WriteTypeParameters(node.GetChildren(Slots.TypeParameter), writer, formattingPolicy); } } @@ -412,7 +412,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor writer.WriteIdentifier(Identifier.Create(name)); break; } - WriteTypeParameters(node.GetChildrenByRole(SlotKind.TypeParameter), writer, formattingPolicy); + WriteTypeParameters(node.GetChildren(Slots.TypeParameter), writer, formattingPolicy); } void WriteTypeParameters(IEnumerable typeParameters, TokenWriter writer, CSharpFormattingOptions formattingPolicy) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 646390844..22df73dd5 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1904,7 +1904,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor public virtual void VisitLabelStatement(LabelStatement labelStatement) { StartNode(labelStatement); - WriteIdentifier(labelStatement.GetChildByRole(SlotKind.Identifier)!); + WriteIdentifier(labelStatement.GetChild(Slots.Identifier)!); WriteToken(Roles.Colon); bool foundLabelledStatement = false; for (AstNode? tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs index a96b751f3..d303d3e50 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/IdentifierExpression.cs @@ -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")] diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs index 0dd95cf65..5331bec74 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/AttributeSection.cs @@ -36,10 +36,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { public string AttributeTarget { get { - return GetChildByRole(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)); } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs b/ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs index 375813004..50828bb82 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/PatternMatching/IdentifierExpressionBackreference.cs @@ -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; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs index 93bc19816..14574fce8 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/SimpleType.cs @@ -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; } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs index 8527a6cdb..bbae1c224 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/EntityDeclaration.cs @@ -42,21 +42,21 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public virtual string Name { get { - return GetChildByRole(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(SlotKind.Identifier)!; } - set { SetChildByRole(SlotKind.Identifier, value); } + get { return GetChild(Slots.Identifier)!; } + set { SetChild(Slots.Identifier, value); } } public virtual AstType ReturnType { - get { return GetChildByRole(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) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs index 57fe55ac3..b3195396e 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/MethodDeclaration.cs @@ -72,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public bool IsExtensionMethod { get { - ParameterDeclaration? pd = GetChildByRole(SlotKind.Parameter); + ParameterDeclaration? pd = GetChild(Slots.Parameter); return pd != null && pd.HasThisModifier; } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs index 9363b453b..6d75d1ff6 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/FixNameCollisions.cs @@ -42,7 +42,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms foreach (var typeDecl in rootNode.DescendantsAndSelf.OfType()) { var memberNames = typeDecl.Members.Select(m => { - var type = m.GetChildByRole(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 if (symbol != null && renamedSymbols.TryGetValue(symbol, out string? newName)) { // An IdentifierExpression / MemberReferenceExpression always carries its name identifier. - node.GetChildByRole(SlotKind.Identifier)!.Name = newName; + node.GetChild(Slots.Identifier)!.Name = newName; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index 52b421ba6..1c138b4fb 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -482,7 +482,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var ci = new ConstructorInitializer { ConstructorInitializerType = type }; // Move arguments from invocation to initializer: - invocation.GetChildrenByRole(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); diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 6b1e8687e..dc473111e 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -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(SlotKind.Identifier) is null) + if (member != null && node.GetChild(Slots.Identifier) is null) { switch (member) {