Browse Source

Expose node.Slot and key child positions on SlotKind

Introduce the successor to node.Role for child-slot identity: the generator
emits a CSharpSlotInfo per [Slot], exposed as node.Slot, plus a shared SlotKind
enum for the polymorphic "is this node in an embedded-statement / condition /
base-type slot?" comparisons a per-node identity cannot express. Migrate the
printer and transform position checks from node.Role to node.Slot and
node.Slot.Kind, and read identifier children and role-keyed writes through the
typed properties. Role is still present and is removed later; output is
unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3807/head
Siegfried Pammer 3 weeks ago committed by Siegfried Pammer
parent
commit
89e7f5b662
  1. 69
      ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
  2. 6
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 42
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  5. 16
      ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs
  6. 2
      ICSharpCode.Decompiler/CSharp/Syntax/AstType.cs
  7. 66
      ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs
  8. 2
      ICSharpCode.Decompiler/CSharp/Syntax/SyntaxTree.cs
  9. 6
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  10. 4
      ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs
  11. 2
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  12. 2
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  13. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  14. 12
      ICSharpCode.Decompiler/Output/TextTokenWriter.cs

69
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

@ -30,7 +30,21 @@ namespace ICSharpCode.Decompiler.Generators; @@ -30,7 +30,21 @@ namespace ICSharpCode.Decompiler.Generators;
[Generator]
internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
{
record AstNodeAdditions(string NodeName, bool NeedsAcceptImpls, bool NeedsVisitor, bool NeedsNullNode, bool NeedsPatternPlaceholder, int NullNodeBaseCtorParamCount, bool IsTypeNode, string VisitMethodName, string VisitMethodParamType, EquatableArray<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? MembersToMatch, EquatableArray<(string RoleExpr, bool IsCollection, string PropertyName, string PropertyType, string ElementType, bool IsOverride, bool IsNullable)>? Slots);
record AstNodeAdditions(string NodeName, bool NeedsAcceptImpls, bool NeedsVisitor, bool NeedsNullNode, bool NeedsPatternPlaceholder, int NullNodeBaseCtorParamCount, bool IsTypeNode, string VisitMethodName, string VisitMethodParamType, EquatableArray<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? MembersToMatch, EquatableArray<(string RoleExpr, bool IsCollection, string PropertyName, string PropertyType, string ElementType, bool IsOverride, bool IsNullable, string KindName)>? Slots);
// Derives the shared SlotKind name from a [Slot] role expression: the last dotted segment with a
// trailing "Role" removed (e.g. "Roles.EmbeddedStatement" -> "EmbeddedStatement", "LeftRole" ->
// "Left", "PropertyDeclaration.GetterRole" -> "Getter"). Roles that share a name (aliases, or the
// same logical slot across node types) intentionally collapse to one kind, so node.Slot.Kind matches
// node.Role comparisons; consumer sites derive the kind from their role the same way.
static string SlotKindName(string roleExpr)
{
int dot = roleExpr.LastIndexOf('.');
string name = dot >= 0 ? roleExpr.Substring(dot + 1) : roleExpr;
if (name.EndsWith("Role") && name.Length > 4)
name = name.Substring(0, name.Length - 4);
return name;
}
AstNodeAdditions GetAstNodeAdditions(GeneratorAttributeSyntaxContext context, CancellationToken ct)
{
@ -78,7 +92,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator @@ -78,7 +92,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
// Collect the slot schema: child properties tagged [Slot], in declaration order. The
// attribute names the Role expression to use; single vs collection comes from the type.
List<(string RoleExpr, bool IsCollection, string PropertyName, string PropertyType, string ElementType, bool IsOverride, bool IsNullable)>? slots = null;
List<(string RoleExpr, bool IsCollection, string PropertyName, string PropertyType, string ElementType, bool IsOverride, bool IsNullable, string KindName)>? slots = null;
foreach (var m in targetSymbol.GetMembers())
{
if (m is not IPropertySymbol property)
@ -96,7 +110,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator @@ -96,7 +110,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
string elementType = isCollection
? ((INamedTypeSymbol)property.Type).TypeArguments[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)
: propertyType;
slots.Add((roleExpr, isCollection, property.Name, propertyType, elementType, property.IsOverride, isNullable));
slots.Add((roleExpr, isCollection, property.Name, propertyType, elementType, property.IsOverride, isNullable, SlotKindName(roleExpr)));
}
return new(targetSymbol.Name, !targetSymbol.MemberNames.Contains("AcceptVisitor"),
@ -293,7 +307,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator @@ -293,7 +307,7 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
// substitutes the role's null object when empty (pre-NRT); a collection slot owns a lazily
// created AstNodeCollection bound to this node. A slot re-declared from an inherited contract
// member (Part I.3 flatten) is an override.
foreach (var (roleExpr, isCollection, name, type, elementType, isOverride, isNullable) in slots)
foreach (var (roleExpr, isCollection, name, type, elementType, isOverride, isNullable, kindName) in slots)
{
string field = FieldName(name);
if (isCollection)
@ -367,6 +381,23 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator @@ -367,6 +381,23 @@ internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));");
builder.AppendLine("\t}");
// One CSharpSlotInfo static per slot; node.Slot compares against these by object identity.
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});");
builder.AppendLine("\tinternal override CSharpSlotInfo GetChildSlotInfo(int index)");
builder.AppendLine("\t{");
builder.AppendLine("\t\tint i = index;");
foreach (var s in slots)
{
if (s.IsCollection)
builder.AppendLine($"\t\t{{ int n = {FieldName(s.PropertyName)}?.Count ?? 0; if (i < n) return {s.PropertyName}Slot; i -= n; }}");
else
builder.AppendLine($"\t\tif (i == 0) return {s.PropertyName}Slot; i -= 1;");
}
builder.AppendLine("\t\tthrow new System.ArgumentOutOfRangeException(nameof(index));");
builder.AppendLine("\t}");
if (slots.Any(s => s.IsCollection))
{
builder.AppendLine("\tinternal override AstNodeCollection? GetCollectionByRole(Role role)");
@ -489,6 +520,36 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -489,6 +520,36 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
context.RegisterSourceOutput(astNodeAdditions, WriteGeneratedMembers);
context.RegisterSourceOutput(visitorMembers, WriteVisitors);
context.RegisterSourceOutput(visitorMembers, WriteSlotKinds);
}
// Emits the SlotKind enum: one value per distinct slot kind across all nodes. A node's CSharpSlotInfo
// carries its kind, and consumers compare node.Slot.Kind == SlotKind.X -- shared across node types, so
// it replaces the old polymorphic node.Role == Roles.X comparisons.
void WriteSlotKinds(SourceProductionContext context, ImmutableArray<AstNodeAdditions> source)
{
var kinds = new SortedSet<string>(StringComparer.Ordinal);
foreach (var node in source)
{
if (node.Slots is { } slots)
{
foreach (var s in slots)
kinds.Add(s.KindName);
}
}
var builder = new StringBuilder();
builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
builder.AppendLine();
builder.AppendLine("/// <summary>Identifies the kind of an AST child slot, shared across node types.</summary>");
builder.AppendLine("public enum SlotKind");
builder.AppendLine("{");
builder.AppendLine("\tNone,");
foreach (var k in kinds)
builder.AppendLine($"\t{k},");
builder.AppendLine("}");
context.AddSource("SlotKind.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
}
}

6
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -765,14 +765,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -765,14 +765,14 @@ namespace ICSharpCode.Decompiler.CSharp
var astBuilder = CreateAstBuilder(decompileRun.Settings);
var attrSection = new AttributeSection(astBuilder.ConvertAttribute(a));
attrSection.AttributeTarget = "assembly";
syntaxTree.AddChild(attrSection, SyntaxTree.MemberRole);
syntaxTree.Members.Add(attrSection);
}
foreach (var a in typeSystem.MainModule.GetModuleAttributes())
{
var astBuilder = CreateAstBuilder(decompileRun.Settings);
var attrSection = new AttributeSection(astBuilder.ConvertAttribute(a));
attrSection.AttributeTarget = "module";
syntaxTree.AddChild(attrSection, SyntaxTree.MemberRole);
syntaxTree.Members.Add(attrSection);
}
}
catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException))
@ -801,7 +801,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -801,7 +801,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (currentNamespace != typeDef.Namespace)
{
groupNode = new NamespaceDeclaration(typeDef.Namespace);
syntaxTree.AddChild(groupNode, SyntaxTree.MemberRole);
syntaxTree.Members.Add(groupNode);
}
}
currentNamespace = typeDef.Namespace;

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

@ -252,8 +252,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -252,8 +252,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary>
protected virtual void Semicolon()
{
// get the role of the current node
Role role = containerStack.Peek().Role;
// get the slot of the current node
SlotKind? kind = containerStack.Peek().Slot?.Kind;
if (!SkipToken())
{
WriteToken(Roles.Semicolon);
@ -265,16 +265,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -265,16 +265,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
bool SkipToken()
{
return role == ForStatement.InitializerRole
|| role == ForStatement.IteratorRole
|| role == UsingStatement.ResourceAcquisitionRole;
return kind == SlotKind.Initializer
|| kind == SlotKind.Iterator
|| kind == SlotKind.ResourceAcquisition;
}
bool SkipNewLine()
{
if (containerStack.Peek() is not Accessor accessor)
return false;
if (!(role == PropertyDeclaration.GetterRole || role == PropertyDeclaration.SetterRole))
if (!(kind == SlotKind.Getter || kind == SlotKind.Setter))
return false;
bool isAutoProperty = accessor.Body.IsNull
&& !accessor.Attributes.Any()
@ -693,11 +693,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -693,11 +693,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
if (node.Parent is ObjectCreateExpression)
{
return node.Role == ObjectCreateExpression.InitializerRole;
return node.Slot?.Kind == SlotKind.Initializer;
}
if (node.Parent is NamedExpression)
{
return node.Role == Roles.Expression;
return node.Slot?.Kind == SlotKind.Expression;
}
return false;
}
@ -1358,7 +1358,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1358,7 +1358,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitQueryExpression(QueryExpression queryExpression)
{
StartNode(queryExpression);
if (queryExpression.Role != QueryContinuationClause.PrecedingQueryRole)
if (queryExpression.Slot?.Kind != SlotKind.PrecedingQuery)
writer.Indent();
bool first = true;
foreach (var clause in queryExpression.Clauses)
@ -1376,7 +1376,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1376,7 +1376,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
clause.AcceptVisitor(this);
}
if (queryExpression.Role != QueryContinuationClause.PrecedingQueryRole)
if (queryExpression.Slot?.Kind != SlotKind.PrecedingQuery)
writer.Unindent();
EndNode(queryExpression);
}
@ -1707,7 +1707,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1707,7 +1707,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
StartNode(usingAliasDeclaration);
WriteKeyword(UsingAliasDeclaration.UsingKeywordRole);
WriteIdentifier(usingAliasDeclaration.GetChildByRole(UsingAliasDeclaration.AliasRole));
WriteIdentifier(usingAliasDeclaration.AliasToken);
Space(policy.SpaceAroundEqualityOperator);
WriteToken(Roles.Assign);
Space(policy.SpaceAroundEqualityOperator);
@ -1912,7 +1912,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1912,7 +1912,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
StartNode(gotoStatement);
WriteKeyword(GotoStatement.GotoKeywordRole);
WriteIdentifier(gotoStatement.GetChildByRole(Roles.Identifier));
WriteIdentifier(gotoStatement.NameToken);
Semicolon();
EndNode(gotoStatement);
}
@ -1957,7 +1957,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1957,7 +1957,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
bool foundLabelledStatement = false;
for (AstNode tmp = labelStatement.NextSibling; tmp != null; tmp = tmp.NextSibling)
{
if (tmp.Role == labelStatement.Role)
if (tmp.Slot?.Kind == labelStatement.Slot?.Kind)
{
foundLabelledStatement = true;
}
@ -2312,12 +2312,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2312,12 +2312,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteAttributes(accessor.Attributes);
WriteModifiers(accessor.Modifiers);
BraceStyle style = policy.StatementBraceStyle;
if (accessor.Role == PropertyDeclaration.GetterRole)
if (accessor.Slot?.Kind == SlotKind.Getter)
{
WriteKeyword("get", PropertyDeclaration.GetKeywordRole);
style = policy.PropertyGetBraceStyle;
}
else if (accessor.Role == PropertyDeclaration.SetterRole)
else if (accessor.Slot?.Kind == SlotKind.Setter)
{
if (accessor.Kind == AccessorKind.Init)
{
@ -2329,12 +2329,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2329,12 +2329,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
style = policy.PropertySetBraceStyle;
}
else if (accessor.Role == CustomEventDeclaration.AddAccessorRole)
else if (accessor.Slot?.Kind == SlotKind.AddAccessor)
{
WriteKeyword("add", CustomEventDeclaration.AddKeywordRole);
style = policy.EventAddBraceStyle;
}
else if (accessor.Role == CustomEventDeclaration.RemoveAccessorRole)
else if (accessor.Slot?.Kind == SlotKind.RemoveAccessor)
{
WriteKeyword("remove", CustomEventDeclaration.RemoveKeywordRole);
style = policy.EventRemoveBraceStyle;
@ -2479,7 +2479,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2479,7 +2479,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// output add/remove in their original order
foreach (AstNode node in customEventDeclaration.Children)
{
if (node.Role == CustomEventDeclaration.AddAccessorRole || node.Role == CustomEventDeclaration.RemoveAccessorRole)
if (node.Slot?.Kind == SlotKind.AddAccessor || node.Slot?.Kind == SlotKind.RemoveAccessor)
{
node.AcceptVisitor(this);
}
@ -2556,7 +2556,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2556,7 +2556,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// output get/set in their original order
foreach (AstNode node in indexerDeclaration.Children)
{
if (node.Role == IndexerDeclaration.GetterRole || node.Role == IndexerDeclaration.SetterRole)
if (node.Slot?.Kind == SlotKind.Getter || node.Slot?.Kind == SlotKind.Setter)
{
node.AcceptVisitor(this);
}
@ -2718,7 +2718,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2718,7 +2718,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// output get/set in their original order
foreach (AstNode node in propertyDeclaration.Children)
{
if (node.Role == IndexerDeclaration.GetterRole || node.Role == IndexerDeclaration.SetterRole)
if (node.Slot?.Kind == SlotKind.Getter || node.Slot?.Kind == SlotKind.Setter)
{
node.AcceptVisitor(this);
}
@ -3183,7 +3183,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -3183,7 +3183,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
break;
default:
WriteIdentifier(documentationReference.GetChildByRole(Roles.Identifier));
WriteIdentifier(documentationReference.NameToken);
break;
}
WriteTypeArguments(documentationReference.TypeArguments);

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

@ -513,7 +513,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -513,7 +513,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
void HandleLambdaOrQuery(Expression expr)
{
if (expr.Role == BinaryOperatorExpression.LeftRole)
if (expr.Slot?.Kind == SlotKind.Left)
Parenthesize(expr);
if (expr.Parent is IsExpression || expr.Parent is AsExpression)
Parenthesize(expr);

16
ICSharpCode.Decompiler/CSharp/Syntax/AstNode.cs

@ -403,6 +403,22 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -403,6 +403,22 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
internal virtual Role GetChildSlot(int index) => throw new ArgumentOutOfRangeException(nameof(index));
// The CSharpSlotInfo for the slot at the given flattened index; generator-emitted per leaf.
internal virtual CSharpSlotInfo GetChildSlotInfo(int index) => throw new ArgumentOutOfRangeException(nameof(index));
/// <summary>
/// The slot this node occupies in its parent, or null if it has no parent. Compare against a
/// node type's generated slot statics (e.g. <c>node.Slot == BinaryOperatorExpression.LeftSlot</c>).
/// </summary>
public CSharpSlotInfo? Slot {
get {
if (parent == null)
return null;
parent.EnsureChildIndices();
return parent.GetChildSlotInfo(childIndex);
}
}
// Returns the collection occupying the slot with the given role, or null if there is none.
// Overridden by the generator for nodes that have collection slots.
internal virtual AstNodeCollection? GetCollectionByRole(Role role) => null;

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

@ -60,7 +60,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -60,7 +60,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return NameLookupMode.TypeInUsingDeclaration;
}
else if (outermostType.Role == Roles.BaseType)
else if (outermostType.Slot?.Kind == SlotKind.BaseType)
{
// Use BaseTypeReference for a type's base type, and for a constraint on a type.
// Do not use it for a constraint on a method.

66
ICSharpCode.Decompiler/CSharp/Syntax/CSharpSlotInfo.cs

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// Describes one child slot of a node type: its name, the declared child type, and whether it is a
/// collection. Each slot has a single generated static instance (e.g.
/// <c>BinaryOperatorExpression.LeftSlot</c>); <c>node.Slot</c> returns the slot a node occupies in
/// its parent, so <c>node.Slot == X.YSlot</c> identifies a node's position by object identity (the
/// successor to <c>node.Role == X.YRole</c>).
/// </summary>
public sealed class CSharpSlotInfo
{
/// <summary>The slot's property name, e.g. "Left", "Body", "Parameters".</summary>
public string Name { get; }
/// <summary>The declared child type (the element type for a collection slot).</summary>
public Type ChildType { get; }
/// <summary>Whether the slot holds a collection of children rather than a single child.</summary>
public bool IsCollection { get; }
/// <summary>
/// Whether the slot is syntactically optional. Populated accurately once the null-object model
/// is replaced by nullable reference types; currently informational.
/// </summary>
public bool IsOptional { get; }
/// <summary>
/// The slot's kind, shared across node types (so <c>node.Slot.Kind == SlotKind.X</c> identifies a
/// node's position the way <c>node.Role == Roles.X</c> did, including polymorphically).
/// </summary>
public SlotKind Kind { get; }
internal CSharpSlotInfo(string name, Type childType, bool isCollection, SlotKind kind, bool isOptional = false)
{
Name = name;
ChildType = childType;
IsCollection = isCollection;
Kind = kind;
IsOptional = isOptional;
}
public override string ToString() => Name;
}
}

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

@ -108,7 +108,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -108,7 +108,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
foreach (var child in curNode.Children)
{
if (!(child is Statement || child is Expression) &&
(child.Role != Roles.TypeMemberRole || ((child is TypeDeclaration || child is DelegateDeclaration) && includeInnerTypes)))
(child.Slot?.Kind != SlotKind.TypeMember || ((child is TypeDeclaration || child is DelegateDeclaration) && includeInnerTypes)))
nodeStack.Push(child);
}
}

6
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -581,7 +581,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -581,7 +581,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (v.Type.IsByRefLike)
return true; // by-ref-like variables always must be initialized at their declaration.
if (v.InsertionPoint.nextNode.Role == ForStatement.InitializerRole)
if (v.InsertionPoint.nextNode.Slot?.Kind == SlotKind.Initializer)
return true; // for-statement initializers always should combine declaration and initialization.
return !context.Settings.SeparateLocalVariableDeclarations;
@ -693,7 +693,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -693,7 +693,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
v.InsertionPoint = v.InsertionPoint.Up();
}
Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole);
Debug.Assert(v.InsertionPoint.nextNode.Slot?.Kind == SlotKind.Statement);
if (v.DefaultInitialization == VariableInitKind.NeedsSkipInit)
{
AstType unsafeType = context.TypeSystemAstBuilder.ConvertType(
@ -770,7 +770,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -770,7 +770,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false;
for (AstNode node = v.FirstUse; node != null; node = node.Parent)
{
if (node.Role == Roles.EmbeddedStatement)
if (node.Slot?.Kind == SlotKind.EmbeddedStatement)
{
return false;
}

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

@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -112,7 +112,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
bool IsElseIf(Statement statement, Statement parent)
{
return parent is IfElseStatement && statement.Role == IfElseStatement.FalseRole;
return parent is IfElseStatement && statement.Slot?.Kind == SlotKind.False;
}
static void InsertBlock(Statement statement)
@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
switch (statement)
{
case IfElseStatement ies:
return parent is IfElseStatement && ies.Role == IfElseStatement.FalseRole;
return parent is IfElseStatement && ies.Slot?.Kind == SlotKind.False;
case VariableDeclarationStatement vds:
case WhileStatement ws:
case DoWhileStatement dws:

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

@ -232,7 +232,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -232,7 +232,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
);
return;
}
if (method.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == Roles.Condition)
if (method.Name == "op_True" && arguments.Length == 1 && invocationExpression.Slot?.Kind == SlotKind.Condition)
{
invocationExpression.ReplaceWith(arguments[0].UnwrapInDirectionExpression());
return;

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

@ -679,7 +679,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -679,7 +679,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
var newBaseType = new InvocationAstType();
baseType.ReplaceWith(newBaseType);
newBaseType.BaseType = baseType;
PrimaryConstructorDecl.Initializer.GetChildrenByRole(Roles.Argument).MoveTo(newBaseType.Arguments);
PrimaryConstructorDecl.Initializer.Arguments.MoveTo(newBaseType.Arguments);
}
PrimaryConstructorDecl.Remove();

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -188,6 +188,7 @@ @@ -188,6 +188,7 @@
<Compile Include="Solution\SolutionCreator.cs" />
<Compile Include="CSharp\Syntax\AstNode.cs" />
<Compile Include="CSharp\Syntax\AstNodeCollection.cs" />
<Compile Include="CSharp\Syntax\CSharpSlotInfo.cs" />
<Compile Include="CSharp\Syntax\AstType.cs" />
<Compile Include="CSharp\Syntax\ComposedType.cs" />
<Compile Include="CSharp\Syntax\DepthFirstAstVisitor.cs" />

12
ICSharpCode.Decompiler/Output/TextTokenWriter.cs

@ -112,18 +112,18 @@ namespace ICSharpCode.Decompiler @@ -112,18 +112,18 @@ namespace ICSharpCode.Decompiler
{
AstNode node = nodeStack.Peek();
var symbol = node.GetSymbol();
if (symbol == null && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression)
if (symbol == null && node.Slot?.Kind == SlotKind.TargetExpression && node.Parent is InvocationExpression)
{
symbol = node.Parent.GetSymbol();
}
if (symbol != null && node.Role == Roles.Type && node.Parent is ObjectCreateExpression)
if (symbol != null && node.Slot?.Kind == SlotKind.Type && node.Parent is ObjectCreateExpression)
{
var ctorSymbol = node.Parent.GetSymbol();
if (ctorSymbol != null)
symbol = ctorSymbol;
}
if (node is IdentifierExpression && node.Role == Roles.TargetExpression && node.Parent is InvocationExpression && symbol is IMember member)
if (node is IdentifierExpression && node.Slot?.Kind == SlotKind.TargetExpression && node.Parent is InvocationExpression && symbol is IMember member)
{
var declaringType = member.DeclaringType;
if (declaringType != null && declaringType.Kind == TypeKind.Delegate)
@ -161,7 +161,7 @@ namespace ICSharpCode.Decompiler @@ -161,7 +161,7 @@ namespace ICSharpCode.Decompiler
return method + gotoStatement.Label;
}
if (node.Role == Roles.TargetExpression && node.Parent is InvocationExpression)
if (node.Slot?.Kind == SlotKind.TargetExpression && node.Parent is InvocationExpression)
{
var symbol = node.Parent.GetSymbol();
if (symbol is LocalFunctionMethod)
@ -184,7 +184,7 @@ namespace ICSharpCode.Decompiler @@ -184,7 +184,7 @@ namespace ICSharpCode.Decompiler
return variable;
}
if (id.Role == QueryJoinClause.IntoIdentifierRole || id.Role == QueryJoinClause.JoinIdentifierRole)
if (id.Slot?.Kind == SlotKind.IntoIdentifier || id.Slot?.Kind == SlotKind.JoinIdentifier)
{
var variable = id.Annotation<ILVariableResolveResult>()?.Variable;
if (variable != null)
@ -426,7 +426,7 @@ namespace ICSharpCode.Decompiler @@ -426,7 +426,7 @@ namespace ICSharpCode.Decompiler
case "object":
var node = nodeStack.Peek();
ISymbol symbol;
if (node.Role == Roles.Type && node.Parent is ObjectCreateExpression)
if (node.Slot?.Kind == SlotKind.Type && node.Parent is ObjectCreateExpression)
{
symbol = node.Parent.GetSymbol();
}

Loading…
Cancel
Save