diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 5665d02226..d3bd87d1e2 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -159,7 +159,7 @@
-
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 15dc35b8a5..ec9cf9f816 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -560,7 +560,7 @@ public async Task Issue3598([ValueSource(nameof(roslyn4OrNewerOptions))] Compile
}
[Test]
- public async Task ExtensionProperties([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
+ public async Task ExtensionEverything([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview | CompilerOptions.NullableEnable);
}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionEverything.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionEverything.cs
new file mode 100644
index 0000000000..712deca178
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionEverything.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+{
+ internal static class EmptyGroups
+ {
+ extension(int)
+ {
+ }
+
+ extension(int x)
+ {
+ }
+
+ extension(int y)
+ {
+ }
+
+ extension(IEnumerable)
+ {
+ }
+
+ extension(IEnumerable x)
+ {
+ }
+
+ extension(IEnumerable y)
+ {
+ }
+
+ extension(Dictionary)
+ {
+ }
+
+ extension(Dictionary x)
+ {
+ }
+
+ extension(Dictionary y)
+ {
+ }
+ }
+
+ internal static class ExtensionEverything
+ {
+ extension(ICollection collection) where T : notnull
+ {
+ public bool IsEmpty => collection.Count == 0;
+
+ public int Test {
+ get {
+ return 42;
+ }
+ set {
+ }
+ }
+
+ public void AddIfNotNull(T item)
+ {
+ if (item != null)
+ {
+ collection.Add(item);
+ }
+ }
+
+ public T2 CastElementAt(int index) where T2 : T
+ {
+ return (T2)(object)collection.ElementAt(index);
+ }
+
+ public static void StaticExtension()
+ {
+ }
+ }
+
+ extension(ExtensionEverythingTestUseSites.Point point)
+ {
+ public double Magnitude => Math.Sqrt(point.X * point.X + point.Y * point.Y);
+ }
+ }
+
+ internal class ExtensionEverythingTestUseSites
+ {
+ public record struct Point(int X, int Y);
+
+ public static void TestExtensionProperty()
+ {
+ Point point = new Point(3, 4);
+ Console.WriteLine(point.X);
+ Console.WriteLine(point.Y);
+ // TODO implement use-site transformation
+ //Console.WriteLine(point.Magnitude);
+ }
+
+ public static void TestExtensionMethods()
+ {
+ List collection = new List();
+ // TODO implement use-site transformation
+ //Console.WriteLine(collection.IsEmpty);
+ collection.AddIfNotNull("Hello");
+ collection.AddIfNotNull(null);
+ //Console.WriteLine(collection.IsEmpty);
+ //Console.WriteLine(collection.Test);
+ //collection.Test = 100;
+ //List.StaticExtension();
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs
deleted file mode 100644
index e76650da8b..0000000000
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-
-namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
-{
- internal static class ExtensionProperties
- {
- extension(ICollection collection) where T : notnull
- {
- public bool IsEmpty => collection.Count == 0;
-
- public int Test {
- get {
- return 42;
- }
- set {
- }
- }
-
- public void AddIfNotNull(T item)
- {
- if (item != null)
- {
- collection.Add(item);
- }
- }
-
- public T2 Cast(int index) where T2 : T
- {
- return (T2)(object)collection.ElementAt(index);
- }
-
- public static void StaticExtension()
- {
- }
- }
- }
-}
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 9fa3c9f5f5..7a96392e2d 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -274,7 +274,7 @@ public CSharpDecompiler(IDecompilerTypeSystem typeSystem, DecompilerSettings set
///
/// The module containing the member.
/// The metadata token/handle of the member. Can be a TypeDef, MethodDef or FieldDef.
- /// THe settings used to determine whether code should be hidden. E.g. if async methods are not transformed, async state machines are included in the decompiled code.
+ /// The settings used to determine whether code should be hidden. E.g. if async methods are not transformed, async state machines are included in the decompiled code.
public static bool MemberIsHidden(MetadataFile module, EntityHandle member, DecompilerSettings settings)
{
if (module == null || member.IsNil)
@@ -295,7 +295,7 @@ public static bool MemberIsHidden(MetadataFile module, EntityHandle member, Deco
if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle))
return true;
if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata))
- return true;
+ return name != "$";
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, methodHandle))
return true;
return false;
@@ -319,7 +319,7 @@ public static bool MemberIsHidden(MetadataFile module, EntityHandle member, Deco
return true;
if (settings.InlineArrays && name.StartsWith("<>y__InlineArray", StringComparison.Ordinal) && name.EndsWith("`1", StringComparison.Ordinal))
return true;
- if (settings.ExtensionMembers && name.StartsWith("<>E__", StringComparison.Ordinal))
+ if (settings.ExtensionMembers && (name.StartsWith("<>E__", StringComparison.Ordinal) || name.StartsWith("$", StringComparison.Ordinal)))
return true;
}
else if (type.IsCompilerGenerated(metadata))
@@ -506,6 +506,7 @@ static TypeSystemAstBuilder CreateAstBuilder(DecompilerSettings settings)
typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift;
typeSystemAstBuilder.SupportOperatorChecked = settings.CheckedOperators;
typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal;
+ typeSystemAstBuilder.SupportExtensionDeclarations = settings.ExtensionMembers;
return typeSystemAstBuilder;
}
@@ -726,6 +727,11 @@ public static CodeMappingInfo GetCodeMappingInfo(MetadataFile module, EntityHand
continue;
try
{
+ if (TryGetExtensionImplementation(module.Metadata, part, out var impl))
+ {
+ connectedMethods.Enqueue(impl);
+ }
+
ReadCodeMappingInfo(module, info, parent, part, connectedMethods, processedNestedTypes);
}
catch (BadImageFormatException)
@@ -911,6 +917,55 @@ TypeDefinitionHandle ExtractDeclaringType(MemberReference memberRef)
}
}
+ private static bool TryGetExtensionImplementation(MetadataReader metadata, MethodDefinitionHandle definitionPart, out MethodDefinitionHandle implementationPart)
+ {
+ implementationPart = default;
+
+ var def = metadata.GetMethodDefinition(definitionPart);
+ var declTypeHandle = def.GetDeclaringType();
+ var declType = metadata.GetTypeDefinition(declTypeHandle);
+ var name = metadata.GetString(def.Name);
+ var containerHandle = declType.GetDeclaringType();
+
+ if (containerHandle.IsNil)
+ return false;
+
+ if (metadata.StringComparer.StartsWith(declType.Name, "<>E__") || metadata.StringComparer.StartsWith(declType.Name, "$"))
+ {
+ implementationPart = FindImplementations(metadata.GetTypeDefinition(containerHandle).GetMethods());
+ }
+ else if (metadata.StringComparer.StartsWith(declType.Name, "$"))
+ {
+ var container = metadata.GetTypeDefinition(containerHandle);
+ var groupHandle = container.GetDeclaringType();
+ if (groupHandle.IsNil)
+ return false;
+
+ implementationPart = FindImplementations(metadata.GetTypeDefinition(groupHandle).GetMethods());
+ }
+ else
+ {
+ return false;
+ }
+
+ return !implementationPart.IsNil;
+
+ MethodDefinitionHandle FindImplementations(MethodDefinitionHandleCollection methods)
+ {
+ foreach (var h in methods)
+ {
+ var m = metadata.GetMethodDefinition(h);
+ if (!metadata.StringComparer.Equals(m.Name, name))
+ continue;
+
+ // TODO : use SignatureBlobComparer to ensure that the correct method is resolved
+
+ return h;
+ }
+ return default;
+ }
+ }
+
///
/// Decompiles the whole module into a single string.
///
@@ -1019,6 +1074,7 @@ public SyntaxTree Decompile(IEnumerable definitions)
bool first = true;
ITypeDefinition parentTypeDef = null;
+ ExtensionInfo parentExtensionInfo = null;
foreach (var entity in definitions)
{
@@ -1038,7 +1094,8 @@ public SyntaxTree Decompile(IEnumerable definitions)
break;
case HandleKind.MethodDefinition:
IMethod method = module.GetDefinition((MethodDefinitionHandle)entity);
- syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), null));
+ parentExtensionInfo = method.ResolveExtensionInfo();
+ syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), parentExtensionInfo));
if (first)
{
parentTypeDef = method.DeclaringTypeDefinition;
@@ -1055,7 +1112,8 @@ public SyntaxTree Decompile(IEnumerable definitions)
break;
case HandleKind.PropertyDefinition:
IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity);
- syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), null));
+ parentExtensionInfo = property.ResolveExtensionInfo();
+ syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo));
if (first)
{
parentTypeDef = property.DeclaringTypeDefinition;
@@ -1086,6 +1144,61 @@ public SyntaxTree Decompile(IEnumerable definitions)
return syntaxTree;
}
+ public SyntaxTree DecompileExtension(EntityHandle handle)
+ {
+ if (handle.IsNil)
+ throw new ArgumentNullException(nameof(handle));
+ syntaxTree = new SyntaxTree();
+ var namespaces = new HashSet();
+ RequiredNamespaceCollector.CollectNamespaces(handle, module, namespaces);
+ var decompileRun = CreateDecompileRun(namespaces);
+
+ switch (handle.Kind)
+ {
+ case HandleKind.TypeDefinition:
+ ITypeDefinition typeDef = module.GetDefinition((TypeDefinitionHandle)handle);
+ syntaxTree.Members.Add(DoDecompile(typeDef, decompileRun, new SimpleTypeResolveContext(typeDef), asExtension: true));
+ RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(typeDef));
+ break;
+ case HandleKind.MethodDefinition:
+ IMethod methodDef = module.GetDefinition((MethodDefinitionHandle)handle);
+ var extensionInfo = methodDef.ResolveExtensionInfo();
+ Debug.Assert(extensionInfo != null);
+ var memberInfo = extensionInfo.InfoOfExtensionMember((IMethod)methodDef.MemberDefinition).GetValueOrDefault();
+ var subst = new TypeParameterSubstitution(memberInfo.ExtensionGroupingTypeParameters, null);
+ methodDef = methodDef.Specialize(subst);
+ EntityDeclaration entity = DoDecompile(methodDef, decompileRun, new SimpleTypeResolveContext(methodDef), extensionInfo);
+ syntaxTree.Members.Add(entity);
+ RemoveAttribute(entity, KnownAttribute.ExtensionMarker);
+ RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(methodDef.DeclaringTypeDefinition));
+ break;
+ case HandleKind.PropertyDefinition:
+ IProperty propDef = module.GetDefinition((PropertyDefinitionHandle)handle);
+ extensionInfo = propDef.ResolveExtensionInfo();
+ Debug.Assert(extensionInfo != null);
+ var accessor = propDef.Getter ?? propDef.Setter;
+ memberInfo = extensionInfo.InfoOfExtensionMember((IMethod)accessor.MemberDefinition).GetValueOrDefault();
+ subst = new TypeParameterSubstitution(memberInfo.ExtensionGroupingTypeParameters, null);
+ propDef = (IProperty)propDef.Specialize(subst);
+ EntityDeclaration prop = DoDecompile(propDef, decompileRun, new SimpleTypeResolveContext(propDef), extensionInfo);
+ syntaxTree.Members.Add(prop);
+ RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
+ if (propDef.Getter != null)
+ {
+ RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.GetterRole), KnownAttribute.ExtensionMarker);
+ }
+ if (propDef.Setter != null)
+ {
+ RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.SetterRole), KnownAttribute.ExtensionMarker);
+ }
+ RunTransforms(syntaxTree, decompileRun, new SimpleTypeResolveContext(propDef.DeclaringTypeDefinition));
+ break;
+ default:
+ throw new NotSupportedException($"HandleKind {handle.Kind} is not supported!");
+ }
+ return syntaxTree;
+ }
+
ITypeDefinition FindCommonDeclaringTypeDefinition(ITypeDefinition a, ITypeDefinition b)
{
if (a == null || b == null)
@@ -1202,6 +1315,9 @@ Expression ForwardParameter(ParameterDeclaration p)
/// The node of the member which new modifier state should be determined.
void SetNewModifier(EntityDeclaration member)
{
+ if (member is ExtensionDeclaration)
+ return;
+
var entity = (IEntity)member.GetSymbol();
var lookup = new MemberLookup(entity.DeclaringTypeDefinition, entity.ParentModule);
@@ -1278,7 +1394,7 @@ void FixParameterNames(EntityDeclaration entity)
}
}
- EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
+ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun, ITypeResolveContext decompilationContext, bool asExtension = false)
{
Debug.Assert(decompilationContext.CurrentTypeDefinition == typeDef);
var watch = System.Diagnostics.Stopwatch.StartNew();
@@ -1288,15 +1404,33 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun
try
{
typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings);
- var entityDecl = typeSystemAstBuilder.ConvertEntity(typeDef);
+ EntityDeclaration entityDecl;
+ if (asExtension)
+ {
+ var extensionInfo = typeDef.DeclaringTypeDefinition?.ExtensionInfo ?? typeDef.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
+ Debug.Assert(extensionInfo != null);
+ extensionInfo.IsExtensionMarkerType(typeDef, out var extensionGroup);
+ entityDecl = typeSystemAstBuilder.ConvertExtension(extensionGroup);
+ }
+ else
+ {
+ entityDecl = typeSystemAstBuilder.ConvertEntity(typeDef);
+ }
+
if (entityDecl is DelegateDeclaration delegateDeclaration)
{
// Fix empty parameter names in delegate declarations
FixParameterNames(delegateDeclaration);
}
- var typeDecl = entityDecl as TypeDeclaration;
- if (typeDecl == null)
+
+ if (entityDecl is not TypeDeclaration typeDecl)
{
+ if (entityDecl is ExtensionDeclaration ext && settings.ExtensionMembers)
+ {
+ var extensionInfo = typeDef.DeclaringTypeDefinition.ExtensionInfo ?? typeDef.DeclaringTypeDefinition.DeclaringTypeDefinition.ExtensionInfo;
+ extensionInfo.IsExtensionMarkerType(typeDef, out var group);
+ DoDecompileExtensionMembers(ext, group.Marker, extensionInfo);
+ }
// e.g. DelegateDeclaration
return entityDecl;
}
@@ -1327,32 +1461,10 @@ EntityDeclaration DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun
if (settings.ExtensionMembers)
{
- foreach (var group in typeDef.ExtensionInfo?.GetGroups() ?? [])
+ foreach (var group in typeDef.ExtensionInfo?.ExtensionGroups ?? [])
{
- var ext = new ExtensionDeclaration();
- ITypeParameter[] typeParameters = group.Key.TypeParameters;
- var subst = new TypeParameterSubstitution(typeParameters, null);
- ext.TypeParameters.AddRange(typeParameters.Select(tp => typeSystemAstBuilder.ConvertTypeParameter(tp)));
- var marker = group.Key.Marker.Specialize(subst);
- ext.ReceiverParameters.Add(typeSystemAstBuilder.ConvertParameter(marker.Parameters.Single()));
- ext.Constraints.AddRange(typeParameters.Select(c => typeSystemAstBuilder.ConvertTypeParameterConstraint(c)));
-
- foreach (var member in group)
- {
- IMember extMember = member.ExtensionMember.Specialize(subst);
- if (member.ExtensionMember.IsAccessor)
- {
- extMember = member.ExtensionMember.AccessorOwner;
- }
- if (entityMap.Contains(extMember) || extMember.MetadataToken.IsNil)
- {
- // Member is already decompiled.
- continue;
- }
- EntityDeclaration extMemberDecl = DoDecompileExtensionMember(extMember, typeDef.ExtensionInfo, decompileRun, decompilationContext);
- ext.Members.Add(extMemberDecl);
- entityMap.Add(extMember, extMemberDecl);
- }
+ var ext = (ExtensionDeclaration)typeSystemAstBuilder.ConvertExtension(group);
+ DoDecompileExtensionMembers(ext, group.Marker, typeDef.ExtensionInfo);
typeDecl.Members.Add(ext);
}
@@ -1478,7 +1590,7 @@ void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler, Partia
{
switch (entity)
{
- case ITypeDefinition td when extensionInfo.IsExtensionGroupingType(td):
+ case ITypeDefinition td when extensionInfo.IsExtensionGroupType(td) || extensionInfo.IsExtensionMarkerType(td, out _):
return;
case IMethod m when extensionInfo.InfoOfImplementationMember(m).HasValue:
return;
@@ -1549,31 +1661,45 @@ void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler, Partia
}
}
}
- }
- private EntityDeclaration DoDecompileExtensionMember(IMember extMember, ExtensionInfo info, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
- {
- switch (extMember)
+ void DoDecompileExtensionMembers(ExtensionDeclaration ext, IMethod marker, ExtensionInfo extensionInfo)
{
- case IProperty p:
- var prop = DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), info);
- RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
- if (p.Getter != null)
+ foreach (var member in extensionInfo.GetMembersOfGroup(marker))
+ {
+ var extMember = member;
+ if (entityMap.Contains(extMember) || extMember.MetadataToken.IsNil)
{
- RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.GetterRole), KnownAttribute.ExtensionMarker);
+ // Member is already decompiled.
+ continue;
}
- if (p.Setter != null)
+ EntityDeclaration extMemberDecl;
+ switch (extMember)
{
- RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.SetterRole), KnownAttribute.ExtensionMarker);
+ case IProperty p:
+ var prop = DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), extensionInfo);
+ RemoveAttribute(prop, KnownAttribute.ExtensionMarker);
+ if (p.Getter != null)
+ {
+ RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.GetterRole), KnownAttribute.ExtensionMarker);
+ }
+ if (p.Setter != null)
+ {
+ RemoveAttribute(prop.GetChildByRole(PropertyDeclaration.SetterRole), KnownAttribute.ExtensionMarker);
+ }
+ extMemberDecl = prop;
+ break;
+ case IMethod m:
+ var meth = DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), extensionInfo);
+ RemoveAttribute(meth, KnownAttribute.ExtensionMarker);
+ extMemberDecl = meth;
+ break;
+ default:
+ throw new NotSupportedException($"Extension member {extMember} is not supported for decompilation.");
}
- return prop;
- case IMethod m:
- var meth = DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), info);
- RemoveAttribute(meth, KnownAttribute.ExtensionMarker);
- return meth;
+ ext.Members.Add(extMemberDecl);
+ entityMap.Add(extMember, extMemberDecl);
+ }
}
-
- throw new NotSupportedException($"Extension member {extMember} is not supported for decompilation.");
}
EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, MetadataFile module)
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
index 6bd362ecbb..fc08e657af 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
@@ -58,6 +58,15 @@ public void ConvertSymbol(ISymbol symbol, TokenWriter writer, CSharpFormattingOp
throw new ArgumentNullException(nameof(formattingPolicy));
TypeSystemAstBuilder astBuilder = CreateAstBuilder();
+
+ if (IsExtension(symbol as ITypeDefinition, out var extensionGroup))
+ {
+ astBuilder.ShowParameterNames = true;
+ }
+ else
+ {
+ extensionGroup = default;
+ }
AstNode node = astBuilder.ConvertSymbol(symbol);
writer.StartNode(node);
if (node is EntityDeclaration entityDecl)
@@ -129,11 +138,21 @@ public void ConvertSymbol(ISymbol symbol, TokenWriter writer, CSharpFormattingOp
else
writer.WriteIdentifier(Identifier.Create(symbol.Name));
- if ((ConversionFlags & ConversionFlags.ShowParameterList) == ConversionFlags.ShowParameterList && HasParameters(symbol))
+ if (ShowParameterList(symbol))
{
writer.WriteToken(symbol.SymbolKind == SymbolKind.Indexer ? Roles.LBracket : Roles.LPar, symbol.SymbolKind == SymbolKind.Indexer ? "[" : "(");
bool first = true;
- foreach (var param in node.GetChildrenByRole(Roles.Parameter))
+ IEnumerable parameters;
+ if (extensionGroup.Marker != null)
+ {
+ var subst = new TypeParameterSubstitution(extensionGroup.TypeParameters, null);
+ parameters = extensionGroup.Marker.Specialize(subst).Parameters.Select(p => astBuilder.ConvertParameter(p));
+ }
+ else
+ {
+ parameters = node.GetChildrenByRole(Roles.Parameter);
+ }
+ foreach (var param in parameters)
{
if ((ConversionFlags & ConversionFlags.ShowParameterModifiers) == 0)
{
@@ -213,18 +232,31 @@ public void ConvertSymbol(ISymbol symbol, TokenWriter writer, CSharpFormattingOp
}
}
- static bool HasParameters(ISymbol e)
+ bool IsExtension(ITypeDefinition? typeDef, out (IMethod Marker, IReadOnlyList TypeParameters) extensionGroup)
+ {
+ extensionGroup = default;
+ if ((ConversionFlags & ConversionFlags.SupportExtensionDeclarations) == 0)
+ return false;
+ if (typeDef == null)
+ return false;
+ var extensionInfo = typeDef.DeclaringTypeDefinition?.ExtensionInfo ?? typeDef.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
+ return extensionInfo != null && extensionInfo.IsExtensionMarkerType(typeDef, out extensionGroup);
+ }
+
+ bool ShowParameterList(ISymbol e)
{
switch (e.SymbolKind)
{
- case SymbolKind.TypeDefinition:
- return ((ITypeDefinition)e).Kind == TypeKind.Delegate;
+ case SymbolKind.TypeDefinition when ((ITypeDefinition)e).Kind is TypeKind.Delegate:
+ return (ConversionFlags & ConversionFlags.ShowParameterList) != 0;
+ case SymbolKind.TypeDefinition when IsExtension((ITypeDefinition)e, out _):
+ return (ConversionFlags & ConversionFlags.SupportExtensionDeclarations) != 0;
case SymbolKind.Indexer:
case SymbolKind.Method:
case SymbolKind.Operator:
case SymbolKind.Constructor:
case SymbolKind.Destructor:
- return true;
+ return (ConversionFlags & ConversionFlags.ShowParameterList) != 0;
default:
return false;
}
@@ -246,6 +278,7 @@ TypeSystemAstBuilder CreateAstBuilder()
astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0;
astBuilder.SupportUnsignedRightShift = (ConversionFlags & ConversionFlags.SupportUnsignedRightShift) != 0;
astBuilder.SupportOperatorChecked = (ConversionFlags & ConversionFlags.SupportOperatorChecked) != 0;
+ astBuilder.SupportExtensionDeclarations = (ConversionFlags & ConversionFlags.SupportExtensionDeclarations) != 0;
return astBuilder;
}
@@ -268,8 +301,16 @@ void WriteTypeDeclarationName(ITypeDefinition typeDef, TokenWriter writer, CShar
writer.WriteToken(Roles.Dot, ".");
}
}
- writer.WriteIdentifier(node.NameToken);
- WriteTypeParameters(node, writer, formattingPolicy);
+ if (IsExtension(typeDef, out var group))
+ {
+ writer.WriteKeyword(ExtensionDeclaration.ExtensionKeywordRole, "extension");
+ WriteTypeParameters(group.TypeParameters.Select(tp => astBuilder.ConvertTypeParameter(tp)), writer, formattingPolicy);
+ }
+ else
+ {
+ writer.WriteIdentifier(node.NameToken);
+ WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter), writer, formattingPolicy);
+ }
}
void WriteMemberDeclarationName(IMember member, TokenWriter writer, CSharpFormattingOptions formattingPolicy)
@@ -371,15 +412,14 @@ void WriteMemberDeclarationName(IMember member, TokenWriter writer, CSharpFormat
writer.WriteIdentifier(Identifier.Create(name));
break;
}
- WriteTypeParameters(node, writer, formattingPolicy);
+ WriteTypeParameters(node.GetChildrenByRole(Roles.TypeParameter), writer, formattingPolicy);
}
- void WriteTypeParameters(EntityDeclaration node, TokenWriter writer, CSharpFormattingOptions formattingPolicy)
+ void WriteTypeParameters(IEnumerable typeParameters, TokenWriter writer, CSharpFormattingOptions formattingPolicy)
{
if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList)
{
var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy);
- IEnumerable typeParameters = node.GetChildrenByRole(Roles.TypeParameter);
if ((ConversionFlags & ConversionFlags.ShowTypeParameterVarianceModifier) == 0)
{
typeParameters = typeParameters.Select(RemoveVarianceModifier);
diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs
index 745854d0d8..4128855644 100644
--- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs
+++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs
@@ -65,6 +65,12 @@ void CollectNamespaces(IEntity entity, MetadataModule module, CodeMappingInfo ma
switch (entity)
{
case ITypeDefinition td:
+ var extensionInfo = td.DeclaringTypeDefinition?.ExtensionInfo ?? td.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
+ if (extensionInfo?.IsExtensionMarkerType(td, out _) == true || extensionInfo?.IsExtensionGroupType(td) == true)
+ {
+ td = extensionInfo.Container;
+ }
+
namespaces.Add(td.Namespace);
HandleAttributes(td.GetAttributes());
HandleTypeParameters(td.TypeParameters);
@@ -76,6 +82,9 @@ void CollectNamespaces(IEntity entity, MetadataModule module, CodeMappingInfo ma
foreach (var nestedType in td.NestedTypes)
{
+ bool isGroupOrMarker = extensionInfo?.IsExtensionGroupType(nestedType) == true || extensionInfo?.IsExtensionMarkerType(nestedType, out _) == true;
+ if (isGroupOrMarker)
+ continue;
CollectNamespaces(nestedType, module, mappingInfo);
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index d8c29dbe78..7643505d5a 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -252,6 +252,11 @@ void InitProperties()
/// Controls whether all fully qualified type names should be prefixed with "global::".
///
public bool AlwaysUseGlobal { get; set; }
+
+ ///
+ /// Controls whether C# 14 "extension" declarations are supported.
+ ///
+ public bool SupportExtensionDeclarations { get; set; }
#endregion
#region Convert Type
@@ -1812,6 +1817,16 @@ public EntityDeclaration ConvertEntity(IEntity entity)
}
}
+ public EntityDeclaration ConvertExtension((IMethod MarkerMethod, IReadOnlyList TypeParameters) group)
+ {
+ var ext = new ExtensionDeclaration();
+ var subst = new TypeParameterSubstitution(group.TypeParameters, []);
+ ext.TypeParameters.AddRange(group.TypeParameters.Select(ConvertTypeParameter));
+ ext.ReceiverParameters.Add(ConvertParameter(group.MarkerMethod.Specialize(subst).Parameters.Single()));
+ ext.Constraints.AddRange(group.TypeParameters.Select(ConvertTypeParameterConstraint));
+ return ext;
+ }
+
EntityDeclaration ConvertTypeDefinition(ITypeDefinition typeDefinition)
{
Modifiers modifiers = Modifiers.None;
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index a70e3b5d3a..845995f43c 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -28,7 +28,7 @@
False
false
- 13
+ 14
true
True
ICSharpCode.Decompiler.snk
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
index 7c0c7171fb..3b9419f10c 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
@@ -53,7 +53,7 @@ class VariableToDeclare
private readonly IField field;
private ILVariable declaredVariable;
- public string Name => field.Name;
+ public string Name => @field.Name;
public bool CanPropagate { get; private set; }
public bool UsesInitialValue { get; set; }
diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs
index ba39d4c3b3..f88a8576e3 100644
--- a/ICSharpCode.Decompiler/Output/IAmbience.cs
+++ b/ICSharpCode.Decompiler/Output/IAmbience.cs
@@ -121,6 +121,10 @@ public enum ConversionFlags
/// Support C# 7.2 private protected.
///
UsePrivateProtectedAccessibility = 0x200000,
+ ///
+ /// Support C# 14 extension declarations.
+ ///
+ SupportExtensionDeclarations = 0x400000,
StandardConversionFlags = ShowParameterNames |
ShowAccessibility |
diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
index ea27174ca8..9a5fe0daf2 100644
--- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
+++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
@@ -243,6 +243,22 @@ public override void WriteKeyword(Role role, string keyword)
output.Write(keyword);
}
+ static bool NeedsFold(AstNode node)
+ {
+ if (node == null)
+ {
+ return false;
+ }
+
+ if (node is EntityDeclaration)
+ return true;
+
+ if (node is BlockStatement { Parent: EntityDeclaration or LocalFunctionDeclarationStatement or AnonymousMethodExpression or LambdaExpression })
+ return true;
+
+ return false;
+ }
+
public override void WriteToken(Role role, string token)
{
switch (token)
@@ -255,17 +271,15 @@ public override void WriteToken(Role role, string token)
}
if (braceLevelWithinType >= 0 || nodeStack.Peek() is TypeDeclaration)
braceLevelWithinType++;
- if (nodeStack.PeekOrDefault() is TypeDeclaration or ExtensionDeclaration or BlockStatement { Parent: EntityDeclaration or LocalFunctionDeclarationStatement or AnonymousMethodExpression or LambdaExpression } || settings.FoldBraces)
- {
+ if (NeedsFold(nodeStack.PeekOrDefault()) || settings.FoldBraces)
output.MarkFoldStart(defaultCollapsed: !settings.ExpandMemberDefinitions && braceLevelWithinType == 1, isDefinition: braceLevelWithinType == 1);
- }
output.Write("{");
break;
case "}":
output.Write('}');
if (role != Roles.RBrace)
break;
- if (nodeStack.PeekOrDefault() is TypeDeclaration or ExtensionDeclaration or BlockStatement { Parent: EntityDeclaration or LocalFunctionDeclarationStatement or AnonymousMethodExpression or LambdaExpression } || settings.FoldBraces)
+ if (NeedsFold(nodeStack.PeekOrDefault()) || settings.FoldBraces)
output.MarkFoldEnd();
if (braceLevelWithinType >= 0)
braceLevelWithinType--;
diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
index 697f64b9d9..79dc9b62a0 100644
--- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
@@ -150,12 +150,16 @@ public enum TypeSystemOptions
///
FirstClassSpanTypes = 0x40000,
///
+ /// If this option is active, extension member groups are detected, otherwise the compiler-generated nested classes are left as-is.
+ ///
+ ExtensionMembers = 0x80000,
+ ///
/// Default settings: typical options for the decompiler, with all C# language features enabled.
///
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
- | RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes
+ | RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes | ExtensionMembers
}
///
@@ -201,6 +205,8 @@ public static TypeSystemOptions GetOptions(DecompilerSettings settings)
typeSystemOptions |= TypeSystemOptions.ParamsCollections;
if (settings.FirstClassSpanTypes)
typeSystemOptions |= TypeSystemOptions.FirstClassSpanTypes;
+ if (settings.ExtensionMembers)
+ typeSystemOptions |= TypeSystemOptions.ExtensionMembers;
return typeSystemOptions;
}
diff --git a/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs b/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs
index c9b45c6683..635b4248dc 100644
--- a/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs
@@ -25,6 +25,7 @@
using System.Reflection.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
+using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.TypeSystem
{
@@ -32,11 +33,14 @@ public class ExtensionInfo
{
readonly Dictionary extensionMemberMap;
readonly Dictionary implementationMemberMap;
+ readonly List<(IMethod Marker, IReadOnlyList TypeParameters)> extensionGroups;
public ExtensionInfo(MetadataModule module, ITypeDefinition extensionContainer)
{
this.extensionMemberMap = new();
this.implementationMemberMap = new();
+ this.extensionGroups = [];
+ this.Container = extensionContainer;
var metadata = module.MetadataFile.Metadata;
@@ -86,7 +90,9 @@ bool TryEncodingV1(ITypeDefinition extGroup)
if (marker == null || hasMultipleMarkers)
return false;
- CollectImplementationMethods(extGroup, marker, extensionMethods, extensionGroupTypeParameters);
+ extensionGroups.Add((marker, extensionGroupTypeParameters));
+
+ CollectImplementationMethods(marker, extensionGroupTypeParameters, extensionMethods);
return true;
}
@@ -111,9 +117,19 @@ bool TryEncodingV2(ITypeDefinition extensionGroupsContainer)
if (marker == null)
continue;
+ ITypeParameter[] extensionGroupTypeParameters = new ITypeParameter[markerType.TypeParameterCount];
+ var markerTypeTypeParameters = metadata.GetTypeDefinition((TypeDefinitionHandle)markerType.MetadataToken).GetGenericParameters();
+
+ foreach (var h in markerTypeTypeParameters.WithIndex())
+ {
+ var tp = metadata.GetGenericParameter(h.Item2);
+ extensionGroupTypeParameters[h.Item1] = MetadataTypeParameter.Create(module, markerType, h.Item1, h.Item2);
+ }
+
+ extensionGroups.Add((marker, extensionGroupTypeParameters));
+
TypeDefinition td = metadata.GetTypeDefinition((TypeDefinitionHandle)extensionGroupsContainer.MetadataToken);
List extensionMethods = [];
- ITypeParameter[] extensionGroupTypeParameters = new ITypeParameter[extensionGroupsContainer.TypeParameterCount];
// For easier access to accessors we use SRM
foreach (var h in td.GetMethods())
@@ -133,25 +149,24 @@ bool TryEncodingV2(ITypeDefinition extensionGroupsContainer)
extensionMethods.Add(method);
}
- CollectImplementationMethods(extensionGroupsContainer, marker, extensionMethods, extensionGroupTypeParameters);
+ CollectImplementationMethods(marker, extensionGroupTypeParameters, extensionMethods);
}
return true;
}
- void CollectImplementationMethods(ITypeDefinition extGroup, IMethod marker, List extensionMethods, ITypeParameter[] extensionGroupTypeParameters)
+ void CollectImplementationMethods(IMethod marker, IReadOnlyList typeParameters, List extensionMethods)
{
+ ITypeDefinition markerType = marker.DeclaringTypeDefinition!;
List<(IMethod extension, IMethod implementation)> implementations = [];
- string[] typeParameterNames = new string[extGroup.TypeParameterCount];
-
foreach (var extension in extensionMethods)
{
- int expectedTypeParameterCount = extension.TypeParameters.Count + extGroup.TypeParameterCount;
+ int expectedTypeParameterCount = extension.TypeParameters.Count + markerType.TypeParameterCount;
bool hasInstance = !extension.IsStatic;
int parameterOffset = hasInstance ? 1 : 0;
int expectedParameterCount = extension.Parameters.Count + parameterOffset;
- TypeParameterSubstitution subst = new TypeParameterSubstitution([], [.. extGroup.TypeArguments, .. extension.TypeArguments]);
+ TypeParameterSubstitution subst = new TypeParameterSubstitution([], [.. markerType.TypeArguments, .. extension.TypeArguments]);
bool IsMatchingImplementation(IMethod impl)
{
@@ -200,48 +215,15 @@ bool IsMatchingImplementation(IMethod impl)
foreach (var (extension, implementation) in implementations)
{
- for (int i = 0; i < extensionGroupTypeParameters.Length; i++)
- {
- if (typeParameterNames[i] == null)
- {
- typeParameterNames[i] = implementation.TypeParameters[i].Name;
- }
- else if (typeParameterNames[i] != implementation.TypeParameters[i].Name)
- {
- // TODO: Handle name conflicts properly
- typeParameterNames[i] = $"T{i + 1}";
- }
- }
- }
-
- for (int i = 0; i < extensionGroupTypeParameters.Length; i++)
- {
- var originalTypeParameter = extGroup.TypeParameters[i];
- if (extensionGroupTypeParameters[i] == null)
- {
- extensionGroupTypeParameters[i] = new DefaultTypeParameter(
- extGroup, i, typeParameterNames[i],
- VarianceModifier.Invariant,
- attributes: originalTypeParameter.GetAttributes().ToArray(),
- originalTypeParameter.HasValueTypeConstraint,
- originalTypeParameter.HasReferenceTypeConstraint,
- originalTypeParameter.HasDefaultConstructorConstraint,
- originalTypeParameter.TypeConstraints.Select(c => c.Type).ToArray(),
- originalTypeParameter.NullabilityConstraint
- );
- }
- }
-
- foreach (var (extension, implementation) in implementations)
- {
- var info = new ExtensionMemberInfo(marker, extension, implementation, extensionGroupTypeParameters);
+ var info = new ExtensionMemberInfo(marker, typeParameters, extension, implementation);
this.extensionMemberMap[extension] = info;
this.implementationMemberMap[implementation] = info;
}
-
}
}
+ public ITypeDefinition Container { get; }
+
public ExtensionMemberInfo? InfoOfExtensionMember(IMethod method)
{
return this.extensionMemberMap.TryGetValue(method, out var value) ? value : null;
@@ -252,18 +234,51 @@ bool IsMatchingImplementation(IMethod impl)
return this.implementationMemberMap.TryGetValue(method, out var value) ? value : null;
}
- public IEnumerable> GetGroups()
+ public bool IsExtensionGroupType(ITypeDefinition td)
+ {
+ return this.extensionGroups.Any(m => m.Marker.DeclaringTypeDefinition?.DeclaringTypeDefinition?.Equals(td) == true);
+ }
+
+ public bool IsExtensionMarkerType(ITypeDefinition td, out (IMethod Marker, IReadOnlyList TypeParameters) extensionGroup)
{
- return this.extensionMemberMap.Values.GroupBy(x => (x.ExtensionMarkerMethod, x.ExtensionGroupingTypeParameters));
+ extensionGroup = default;
+ foreach (var group in this.extensionGroups)
+ {
+ if (group.Marker.DeclaringTypeDefinition?.Equals(td) == true)
+ {
+ extensionGroup = group;
+ return true;
+ }
+ }
+ return false;
}
- public bool IsExtensionGroupingType(ITypeDefinition type)
+ public IEnumerable GetMembersOfGroup(IMethod marker)
{
- return this.extensionMemberMap.Values.Any(x => x.ExtensionGroupingType.Equals(type));
+ return GetMembers().Distinct();
+
+ IEnumerable GetMembers()
+ {
+ var markerType = marker.DeclaringTypeDefinition;
+ Debug.Assert(markerType != null, "Marker should always be contained in a type");
+
+ foreach (var info in extensionMemberMap.Values)
+ {
+ if (!markerType.Equals(info.ExtensionMarkerType))
+ continue;
+ var subst = new TypeParameterSubstitution(info.ExtensionGroupingTypeParameters, null);
+ if (info.ExtensionMember.IsAccessor)
+ yield return info.ExtensionMember.AccessorOwner.Specialize(subst);
+ else
+ yield return info.ExtensionMember.Specialize(subst);
+ }
+ }
}
+
+ public IReadOnlyList<(IMethod Marker, IReadOnlyList TypeParameters)> ExtensionGroups => this.extensionGroups;
}
- public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation, ITypeParameter[] extensionGroupingTypeParameters)
+ public readonly struct ExtensionMemberInfo(IMethod marker, IReadOnlyList typeParameters, IMethod extension, IMethod implementation)
{
///
/// Metadata-only method called '<Extension>$'. Has the C# signature for the extension declaration.
@@ -282,6 +297,11 @@ public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IM
///
public readonly IMethod ImplementationMethod = implementation;
+ ///
+ /// This is the array of type parameters for the extension declaration.
+ ///
+ public readonly IReadOnlyList ExtensionGroupingTypeParameters = typeParameters;
+
///
/// This is the enclosing static class.
///
@@ -293,11 +313,6 @@ public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IM
///
public ITypeDefinition ExtensionGroupingType => ExtensionMember.DeclaringTypeDefinition!;
- ///
- /// This is the array of type parameters for the extension declaration.
- ///
- public ITypeParameter[] ExtensionGroupingTypeParameters => extensionGroupingTypeParameters;
-
///
/// This class holds the type parameters for the extension declaration with full fidelity of C# constraints.
///
diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
index 9bd99a3890..41bca37e9c 100644
--- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs
@@ -66,6 +66,9 @@ public interface IMethod : IParameterizedMember
///
IReadOnlyList TypeArguments { get; }
+ ///
+ /// Returns true for classic extension methods, where extension method == implementation method, otherwise returns false.
+ ///
bool IsExtensionMethod { get; }
bool IsLocalFunction { get; }
bool IsConstructor { get; }
diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
index 100fc9c6ab..f4c9295ded 100644
--- a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
@@ -28,7 +28,6 @@ namespace ICSharpCode.Decompiler.TypeSystem
///
public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity
{
- ExtensionInfo? ExtensionInfo { get; }
IReadOnlyList NestedTypes { get; }
IReadOnlyList Members { get; }
@@ -77,6 +76,13 @@ public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity
/// This property is used to speed up the search for extension members.
bool HasExtensions { get; }
+ ///
+ /// For types containing extension blocks, returns a non-null value.
+ /// For extension blocks, returns the extension info of the parent.
+ /// For all other types returns null.
+ ///
+ ExtensionInfo? ExtensionInfo { get; }
+
///
/// The nullability specified in the [NullableContext] attribute on the type.
/// This serves as default nullability for members of the type that do not have a [Nullable] attribute.
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
index 5d820838ae..9fda3fc712 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
@@ -150,6 +150,8 @@ public ExtensionInfo ExtensionInfo {
get {
if (!HasExtensions)
return null;
+ if ((module.TypeSystemOptions & TypeSystemOptions.ExtensionMembers) == 0)
+ return null;
var extensionInfo = LazyInit.VolatileRead(ref this.extensionInfo);
if (extensionInfo != null)
return extensionInfo;
@@ -203,8 +205,8 @@ public IEnumerable Fields {
var fieldList = new List(fieldCollection.Count);
foreach (FieldDefinitionHandle h in fieldCollection)
{
- var field = metadata.GetFieldDefinition(h);
- var attr = field.Attributes;
+ var @field = metadata.GetFieldDefinition(h);
+ var attr = @field.Attributes;
if (module.IsVisible(attr))
{
fieldList.Add(module.GetDefinition(h));
diff --git a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs
index a6b8349d46..9701bba081 100644
--- a/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs
@@ -274,7 +274,7 @@ public IProperty GetDefinition(PropertyDefinitionHandle handle)
return new MetadataProperty(this, handle);
int row = MetadataTokens.GetRowNumber(handle);
Debug.Assert(row != 0);
- if (row >= methodDefs.Length)
+ if (row >= propertyDefs.Length)
HandleOutOfRange(handle);
var property = LazyInit.VolatileRead(ref propertyDefs[row]);
if (property != null)
@@ -291,7 +291,7 @@ public IEvent GetDefinition(EventDefinitionHandle handle)
return new MetadataEvent(this, handle);
int row = MetadataTokens.GetRowNumber(handle);
Debug.Assert(row != 0);
- if (row >= methodDefs.Length)
+ if (row >= eventDefs.Length)
HandleOutOfRange(handle);
var ev = LazyInit.VolatileRead(ref eventDefs[row]);
if (ev != null)
diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
index b89e2d10b3..90bf02ce64 100644
--- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
@@ -871,5 +872,16 @@ public static IType AsParameterizedType(this ITypeDefinition td)
}
return ns;
}
+
+ public static ExtensionInfo? ResolveExtensionInfo(this IMember member)
+ {
+ if (member is null)
+ {
+ throw new ArgumentNullException(nameof(member));
+ }
+ var td = member.DeclaringTypeDefinition;
+ Debug.Assert(td != null, "IMember.DeclaringTypeDefinition should never be null");
+ return td.DeclaringTypeDefinition?.ExtensionInfo ?? td.DeclaringTypeDefinition?.DeclaringTypeDefinition?.ExtensionInfo;
+ }
}
}
diff --git a/ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs b/ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs
index 2ab14a0acb..5301308549 100644
--- a/ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs
+++ b/ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs
@@ -24,8 +24,8 @@ namespace ICSharpCode.ILSpyX.Abstractions
{
public interface ITreeNode
{
- object Text { get; }
- object Icon { get; }
+ object? Text { get; }
+ object? Icon { get; }
IEnumerable Children { get; }
void EnsureLazyChildren();
diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs
index 3ff91f6022..27f27b16e9 100644
--- a/ILSpy/ExtensionMethods.cs
+++ b/ILSpy/ExtensionMethods.cs
@@ -76,7 +76,7 @@ public static U[] SelectArray(this ICollection collection, Func f
return result;
}
- public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion languageVersion)
+ public static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(this MetadataFile file, SettingsService settingsService, LanguageVersion? languageVersion)
{
var decompilerSettings = settingsService.DecompilerSettings.Clone();
if (!Enum.TryParse(languageVersion?.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
diff --git a/ILSpy/Images/Images.cs b/ILSpy/Images/Images.cs
index 3b1af7e21a..5d5fd92494 100644
--- a/ILSpy/Images/Images.cs
+++ b/ILSpy/Images/Images.cs
@@ -119,6 +119,7 @@ static ImageSource Load(string icon)
private static readonly ImageSource OverlayReference = Load("ReferenceOverlay");
private static readonly ImageSource OverlayStatic = Load("OverlayStatic");
+ private static readonly ImageSource OverlayExtension = Load("OverlayExtension");
public static readonly ImageSource TypeReference = GetIcon("ShowPublicOnly", "ReferenceOverlay");
public static readonly ImageSource MethodReference = GetIcon("Method", "ReferenceOverlay");
@@ -203,16 +204,16 @@ private static bool ResourceExists(Uri uri)
private static readonly TypeIconCache typeIconCache = new TypeIconCache();
private static readonly MemberIconCache memberIconCache = new MemberIconCache();
- public static ImageSource GetIcon(TypeIcon icon, AccessOverlayIcon overlay, bool isStatic = false)
+ public static ImageSource GetIcon(TypeIcon icon, AccessOverlayIcon overlay, bool isStatic = false, bool isExtension = false)
{
lock (typeIconCache)
- return typeIconCache.GetIcon(icon, overlay, isStatic);
+ return typeIconCache.GetIcon(icon, overlay, isStatic, isExtension);
}
- public static ImageSource GetIcon(MemberIcon icon, AccessOverlayIcon overlay, bool isStatic)
+ public static ImageSource GetIcon(MemberIcon icon, AccessOverlayIcon overlay, bool isStatic, bool isExtension)
{
lock (memberIconCache)
- return memberIconCache.GetIcon(icon, overlay, isStatic);
+ return memberIconCache.GetIcon(icon, overlay, isStatic, isExtension);
}
public static AccessOverlayIcon GetOverlayIcon(Accessibility accessibility)
@@ -236,37 +237,45 @@ public static AccessOverlayIcon GetOverlayIcon(Accessibility accessibility)
}
}
- private static ImageSource GetIcon(string baseImage, string overlay = null, bool isStatic = false)
+ private static ImageSource GetIcon(string baseImage, string overlay = null, bool isStatic = false, bool isExtension = false)
{
ImageSource baseImageSource = Load(baseImage);
ImageSource overlayImageSource = overlay != null ? Load(overlay) : null;
- return CreateOverlayImage(baseImageSource, overlayImageSource, isStatic);
+ return CreateOverlayImage(baseImageSource, overlayImageSource, isStatic, isExtension);
}
- private static ImageSource CreateOverlayImage(ImageSource baseImage, ImageSource overlay, bool isStatic)
+ private static ImageSource CreateOverlayImage(ImageSource baseImage, ImageSource overlay, bool isStatic, bool isExtension)
{
var group = new DrawingGroup();
+ var baseDrawingGroup = new DrawingGroup();
Drawing baseDrawing = new ImageDrawing(baseImage, iconRect);
- if (overlay != null)
- {
- var nestedGroup = new DrawingGroup { Transform = new ScaleTransform(0.8, 0.8) };
- nestedGroup.Children.Add(baseDrawing);
- group.Children.Add(nestedGroup);
- group.Children.Add(new ImageDrawing(overlay, iconRect));
- }
- else
+ baseDrawingGroup.Children.Add(baseDrawing);
+
+ if (isExtension)
{
- group.Children.Add(baseDrawing);
+ var extensionGroup = new DrawingGroup();
+ extensionGroup.Children.Add(baseDrawingGroup);
+ baseDrawingGroup.Transform = new ScaleTransform(0.8, 0.8);
+ extensionGroup.Children.Add(new ImageDrawing(Images.OverlayExtension, iconRect));
+ baseDrawingGroup = extensionGroup;
}
+ group.Children.Add(baseDrawingGroup);
+
if (isStatic)
{
group.Children.Add(new ImageDrawing(Images.OverlayStatic, iconRect));
}
+ if (overlay != null)
+ {
+ baseDrawingGroup.Transform = new ScaleTransform(0.8, 0.8);
+ group.Children.Add(new ImageDrawing(overlay, iconRect));
+ }
+
var image = new DrawingImage(group);
if (image.CanFreeze)
{
@@ -330,7 +339,6 @@ public MemberIconCache()
PreloadPublicIconToCache(MemberIcon.Constructor, Images.Constructor);
PreloadPublicIconToCache(MemberIcon.VirtualMethod, Images.VirtualMethod);
PreloadPublicIconToCache(MemberIcon.Operator, Images.Operator);
- PreloadPublicIconToCache(MemberIcon.ExtensionMethod, Images.ExtensionMethod);
PreloadPublicIconToCache(MemberIcon.PInvokeMethod, Images.PInvokeMethod);
PreloadPublicIconToCache(MemberIcon.Event, Images.Event);
}
@@ -370,9 +378,6 @@ protected override ImageSource GetBaseImage(MemberIcon icon)
case MemberIcon.Operator:
baseImage = Images.Operator;
break;
- case MemberIcon.ExtensionMethod:
- baseImage = Images.ExtensionMethod;
- break;
case MemberIcon.PInvokeMethod:
baseImage = Images.PInvokeMethod;
break;
@@ -389,35 +394,35 @@ protected override ImageSource GetBaseImage(MemberIcon icon)
private abstract class IconCache
{
- private readonly Dictionary<(T, AccessOverlayIcon, bool), ImageSource> cache = new Dictionary<(T, AccessOverlayIcon, bool), ImageSource>();
+ private readonly Dictionary<(T, AccessOverlayIcon, bool, bool), ImageSource> cache = new Dictionary<(T, AccessOverlayIcon, bool, bool), ImageSource>();
protected void PreloadPublicIconToCache(T icon, ImageSource image)
{
- var iconKey = (icon, AccessOverlayIcon.Public, false);
+ var iconKey = (icon, AccessOverlayIcon.Public, false, false);
cache.Add(iconKey, image);
}
- public ImageSource GetIcon(T icon, AccessOverlayIcon overlay, bool isStatic)
+ public ImageSource GetIcon(T icon, AccessOverlayIcon overlay, bool isStatic, bool isExtension)
{
- var iconKey = (icon, overlay, isStatic);
+ var iconKey = (icon, overlay, isStatic, isExtension);
if (cache.ContainsKey(iconKey))
{
return cache[iconKey];
}
else
{
- ImageSource result = BuildMemberIcon(icon, overlay, isStatic);
+ ImageSource result = BuildMemberIcon(icon, overlay, isStatic, isExtension);
cache.Add(iconKey, result);
return result;
}
}
- private ImageSource BuildMemberIcon(T icon, AccessOverlayIcon overlay, bool isStatic)
+ private ImageSource BuildMemberIcon(T icon, AccessOverlayIcon overlay, bool isStatic, bool isExtension)
{
ImageSource baseImage = GetBaseImage(icon);
ImageSource overlayImage = GetOverlayImage(overlay);
- return CreateOverlayImage(baseImage, overlayImage, isStatic);
+ return CreateOverlayImage(baseImage, overlayImage, isStatic, isExtension);
}
protected abstract ImageSource GetBaseImage(T icon);
diff --git a/ILSpy/Images/MemberIcon.cs b/ILSpy/Images/MemberIcon.cs
index 89a9cb7721..71214e7e0f 100644
--- a/ILSpy/Images/MemberIcon.cs
+++ b/ILSpy/Images/MemberIcon.cs
@@ -30,7 +30,6 @@ internal enum MemberIcon
Constructor,
VirtualMethod,
Operator,
- ExtensionMethod,
PInvokeMethod,
Event
}
diff --git a/ILSpy/Images/OverlayExtension.svg b/ILSpy/Images/OverlayExtension.svg
new file mode 100644
index 0000000000..231935c44c
--- /dev/null
+++ b/ILSpy/Images/OverlayExtension.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/ILSpy/Images/OverlayExtension.xaml b/ILSpy/Images/OverlayExtension.xaml
new file mode 100644
index 0000000000..94370168f4
--- /dev/null
+++ b/ILSpy/Images/OverlayExtension.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 2e0ef396d8..2181ec163f 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -364,6 +364,39 @@ public override void DecompileType(ITypeDefinition type, ITextOutput output, Dec
WriteCode(output, options.DecompilerSettings, decompiler.Decompile(type.MetadataToken), decompiler.TypeSystem);
}
+ public void DecompileExtension(ITypeDefinition extension, ITextOutput output, DecompilationOptions options)
+ {
+ MetadataFile assembly = extension.ParentModule.MetadataFile;
+ CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
+ AddReferenceAssemblyWarningMessage(assembly, output);
+ AddReferenceWarningMessage(assembly, output);
+ WriteCommentLine(output, assembly.FullName);
+ WriteCommentLine(output, TypeToString(extension, ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames | ConversionFlags.SupportExtensionDeclarations));
+ WriteCode(output, options.DecompilerSettings, decompiler.DecompileExtension(extension.MetadataToken), decompiler.TypeSystem);
+ }
+
+ public void DecompileExtension(IMethod extension, ITextOutput output, DecompilationOptions options)
+ {
+ MetadataFile assembly = extension.ParentModule.MetadataFile;
+ CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
+ AddReferenceAssemblyWarningMessage(assembly, output);
+ AddReferenceWarningMessage(assembly, output);
+ WriteCommentLine(output, assembly.FullName);
+ WriteCommentLine(output, TypeToString(extension.DeclaringType, ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames | ConversionFlags.SupportExtensionDeclarations));
+ WriteCode(output, options.DecompilerSettings, decompiler.DecompileExtension(extension.MetadataToken), decompiler.TypeSystem);
+ }
+
+ public void DecompileExtension(IProperty extension, ITextOutput output, DecompilationOptions options)
+ {
+ MetadataFile assembly = extension.ParentModule.MetadataFile;
+ CSharpDecompiler decompiler = CreateDecompiler(assembly, options);
+ AddReferenceAssemblyWarningMessage(assembly, output);
+ AddReferenceWarningMessage(assembly, output);
+ WriteCommentLine(output, assembly.FullName);
+ WriteCommentLine(output, TypeToString(extension.DeclaringType, ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames | ConversionFlags.SupportExtensionDeclarations));
+ WriteCode(output, options.DecompilerSettings, decompiler.DecompileExtension(extension.MetadataToken), decompiler.TypeSystem);
+ }
+
void AddReferenceWarningMessage(MetadataFile module, ITextOutput output)
{
var loadedAssembly = AssemblyTreeModel.AssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module);
diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
index 96f75220e8..d960ecd14d 100644
--- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
+++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs
@@ -71,7 +71,7 @@ internal struct EventDefEntry : IMemberTreeNode
IEntity IMemberTreeNode.Member {
get {
- return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
+ return ((MetadataModule)GetTypeSystemWithCurrentOptionsOrNull(metadataFile)?.MainModule)?.GetDefinition(handle);
}
}
diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs
index c66be35c67..229c92afa8 100644
--- a/ILSpy/TreeNodes/AssemblyTreeNode.cs
+++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs
@@ -259,7 +259,7 @@ protected override void LoadChildren()
void LoadChildrenForExecutableFile(MetadataFile module)
{
- typeSystem = LoadedAssembly.GetTypeSystemOrNull();
+ typeSystem = module.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion);
var assembly = (MetadataModule)typeSystem.MainModule;
this.Children.Add(new MetadataTreeNode(module, Resources.Metadata));
Decompiler.DebugInfo.IDebugInfoProvider debugInfo = LoadedAssembly.GetDebugInfoOrNull();
diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs
index 219a5e17c0..2714eef7f6 100644
--- a/ILSpy/TreeNodes/EventTreeNode.cs
+++ b/ILSpy/TreeNodes/EventTreeNode.cs
@@ -68,7 +68,7 @@ public static object GetText(IEvent ev, Language language, bool includeDeclaring
public static ImageSource GetIcon(IEvent @event)
{
- return Images.GetIcon(MemberIcon.Event, Images.GetOverlayIcon(@event.Accessibility), @event.IsStatic);
+ return Images.GetIcon(MemberIcon.Event, Images.GetOverlayIcon(@event.Accessibility), @event.IsStatic, false);
}
public override FilterResult Filter(LanguageSettings settings)
diff --git a/ILSpy/TreeNodes/ExtensionTreeNode.cs b/ILSpy/TreeNodes/ExtensionTreeNode.cs
new file mode 100644
index 0000000000..50420a9afc
--- /dev/null
+++ b/ILSpy/TreeNodes/ExtensionTreeNode.cs
@@ -0,0 +1,107 @@
+// Copyright (c) 2026 Siegfried Pammer
+//
+// 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.
+
+using System;
+
+using ICSharpCode.Decompiler;
+
+using SRM = System.Reflection.Metadata;
+
+namespace ICSharpCode.ILSpy.TreeNodes
+{
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+
+ using ICSharpCode.Decompiler.Output;
+ using ICSharpCode.Decompiler.TypeSystem;
+
+ public sealed class ExtensionTreeNode : ILSpyTreeNode
+ {
+ public ExtensionTreeNode(ITypeDefinition typeDefinition, (IMethod Marker, IReadOnlyList TypeParameters) extensionGroup, AssemblyTreeNode parentAssemblyNode)
+ {
+ this.ParentAssemblyNode = parentAssemblyNode ?? throw new ArgumentNullException(nameof(parentAssemblyNode));
+ this.ContainerTypeDefinition = typeDefinition ?? throw new ArgumentNullException(nameof(typeDefinition));
+ this.MarkerMethod = extensionGroup.Marker ?? throw new ArgumentNullException(nameof(extensionGroup.Marker));
+ this.TypeParameters = extensionGroup.TypeParameters ?? throw new ArgumentNullException(nameof(extensionGroup.TypeParameters));
+ this.LazyLoading = true;
+ }
+
+ public ITypeDefinition ContainerTypeDefinition { get; }
+
+ public IMethod MarkerMethod { get; }
+
+ public IReadOnlyList TypeParameters { get; }
+
+ public AssemblyTreeNode ParentAssemblyNode { get; }
+
+ public override object Icon => Images.GetIcon(TypeIcon.Class, AccessOverlayIcon.Public, false, true);
+
+ public override object Text => this.Language.TypeToString(GetTypeDefinition(), ConversionFlags.SupportExtensionDeclarations);
+
+ public override object NavigationText => this.Language.TypeToString(GetTypeDefinition(), ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames | ConversionFlags.SupportExtensionDeclarations);
+
+ private ITypeDefinition GetTypeDefinition()
+ {
+ return ((MetadataModule)ParentAssemblyNode.LoadedAssembly
+ .GetMetadataFileOrNull()
+ ?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
+ ?.MainModule)?.GetDefinition((SRM.TypeDefinitionHandle)MarkerMethod.DeclaringTypeDefinition.MetadataToken)
+ ?? MarkerMethod.DeclaringTypeDefinition;
+ }
+
+ protected override void LoadChildren()
+ {
+ var extensionInfo = ContainerTypeDefinition.ExtensionInfo;
+ var members = extensionInfo.GetMembersOfGroup(MarkerMethod).ToList();
+
+ foreach (var property in members.OfType().OrderBy(p => p.Name, NaturalStringComparer.Instance))
+ {
+ this.Children.Add(new PropertyTreeNode(property));
+ }
+ foreach (var method in members.OfType().OrderBy(m => m.Name, NaturalStringComparer.Instance))
+ {
+ if (method.MetadataToken.IsNil)
+ continue;
+ this.Children.Add(new MethodTreeNode(method));
+ }
+ }
+
+ public override FilterResult Filter(LanguageSettings settings)
+ {
+ if (LanguageService.Language is not CSharpLanguage)
+ return FilterResult.Hidden;
+
+ var decompilerSettings = SettingsService.DecompilerSettings.Clone();
+ if (!Enum.TryParse(AssemblyTreeModel.CurrentLanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
+ languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+ decompilerSettings.SetLanguageVersion(languageVersion);
+
+ if (!decompilerSettings.ExtensionMembers)
+ return FilterResult.Hidden;
+
+ return base.Filter(settings);
+ }
+
+ public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
+ {
+ Debug.Assert(language is CSharpLanguage);
+ ((CSharpLanguage)language).DecompileExtension(GetTypeDefinition(), output, options);
+ }
+ }
+}
diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs
index 86d56814f2..0c55273bf9 100644
--- a/ILSpy/TreeNodes/FieldTreeNode.cs
+++ b/ILSpy/TreeNodes/FieldTreeNode.cs
@@ -61,15 +61,15 @@ public static object GetText(IField field, Language language, bool includeDeclar
public static ImageSource GetIcon(IField field)
{
if (field.DeclaringType.Kind == TypeKind.Enum && field.ReturnType.Kind == TypeKind.Enum)
- return Images.GetIcon(MemberIcon.EnumValue, Images.GetOverlayIcon(field.Accessibility), false);
+ return Images.GetIcon(MemberIcon.EnumValue, Images.GetOverlayIcon(field.Accessibility), false, false);
if (field.IsConst)
- return Images.GetIcon(MemberIcon.Literal, Images.GetOverlayIcon(field.Accessibility), false);
+ return Images.GetIcon(MemberIcon.Literal, Images.GetOverlayIcon(field.Accessibility), false, false);
if (field.IsReadOnly)
- return Images.GetIcon(MemberIcon.FieldReadOnly, Images.GetOverlayIcon(field.Accessibility), field.IsStatic);
+ return Images.GetIcon(MemberIcon.FieldReadOnly, Images.GetOverlayIcon(field.Accessibility), field.IsStatic, false);
- return Images.GetIcon(MemberIcon.Field, Images.GetOverlayIcon(field.Accessibility), field.IsStatic);
+ return Images.GetIcon(MemberIcon.Field, Images.GetOverlayIcon(field.Accessibility), field.IsStatic, false);
}
public override FilterResult Filter(LanguageSettings settings)
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index b24f911397..d45eb58ac5 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -16,6 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
@@ -25,6 +27,7 @@
using System.Windows.Threading;
using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Docking;
@@ -48,6 +51,8 @@ protected ILSpyTreeNode()
public Language Language => LanguageService.Language;
+ protected static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(MetadataFile metadataFile) => metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion);
+
protected static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue();
protected static ICollection ResourceNodeFactories { get; } = App.ExportProvider.GetExportedValues().ToArray();
@@ -133,7 +138,7 @@ void ApplyFilterToChild(ILSpyTreeNode child)
}
}
- protected virtual void Settings_Changed(object sender, PropertyChangedEventArgs e)
+ protected virtual void Settings_Changed(object? sender, PropertyChangedEventArgs e)
{
if (sender is not ILSpy.LanguageSettings)
return;
diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs
index 39db2323a2..81cd68fad1 100644
--- a/ILSpy/TreeNodes/MethodTreeNode.cs
+++ b/ILSpy/TreeNodes/MethodTreeNode.cs
@@ -46,9 +46,10 @@ public MethodTreeNode(IMethod method)
private IMethod GetMethodDefinition()
{
- return ((MetadataModule)MethodDefinition.ParentModule?.MetadataFile
+ var m = ((MetadataModule)MethodDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
- ?.MainModule)?.GetDefinition((MethodDefinitionHandle)MethodDefinition.MetadataToken) ?? MethodDefinition;
+ ?.MainModule)?.GetDefinition((MethodDefinitionHandle)MethodDefinition.MetadataToken);
+ return m?.Specialize(MethodDefinition.Substitution) ?? MethodDefinition;
}
public static object GetText(IMethod method, Language language, bool includeDeclaringTypeName = false)
@@ -60,31 +61,47 @@ public static object GetText(IMethod method, Language language, bool includeDecl
public static ImageSource GetIcon(IMethod method)
{
+ bool isExtensionMethod = method.ResolveExtensionInfo()?.InfoOfExtensionMember((IMethod)method.MemberDefinition).HasValue == true
+ || method.IsExtensionMethod;
+
if (method.IsOperator)
- return Images.GetIcon(MemberIcon.Operator, Images.GetOverlayIcon(method.Accessibility), false);
+ return Images.GetIcon(MemberIcon.Operator, Images.GetOverlayIcon(method.Accessibility), false, isExtensionMethod);
- if (method.IsExtensionMethod)
- return Images.GetIcon(MemberIcon.ExtensionMethod, Images.GetOverlayIcon(method.Accessibility), false);
+ if (isExtensionMethod)
+ return Images.GetIcon(MemberIcon.Method, Images.GetOverlayIcon(method.Accessibility), false, true);
if (method.IsConstructor)
- return Images.GetIcon(MemberIcon.Constructor, Images.GetOverlayIcon(method.Accessibility), method.IsStatic);
+ return Images.GetIcon(MemberIcon.Constructor, Images.GetOverlayIcon(method.Accessibility), method.IsStatic, false);
if (!method.HasBody && method.HasAttribute(KnownAttribute.DllImport))
- return Images.GetIcon(MemberIcon.PInvokeMethod, Images.GetOverlayIcon(method.Accessibility), true);
+ return Images.GetIcon(MemberIcon.PInvokeMethod, Images.GetOverlayIcon(method.Accessibility), true, false);
return Images.GetIcon(method.IsVirtual ? MemberIcon.VirtualMethod : MemberIcon.Method,
- Images.GetOverlayIcon(method.Accessibility), method.IsStatic);
+ Images.GetOverlayIcon(method.Accessibility), method.IsStatic, false);
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
- language.DecompileMethod(MethodDefinition, output, options);
+ if (Parent is ExtensionTreeNode && language is CSharpLanguage cs)
+ cs.DecompileExtension(MethodDefinition, output, options);
+ else
+ language.DecompileMethod(MethodDefinition, output, options);
}
public override FilterResult Filter(LanguageSettings settings)
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
+ // hide implementation methods of extension blocks in the tree view
+ if (Language is CSharpLanguage && MethodDefinition.DeclaringTypeDefinition?.ExtensionInfo is { } extInfo)
+ {
+ var decompilerSettings = SettingsService.DecompilerSettings.Clone();
+ if (!Enum.TryParse(LanguageService.LanguageVersion?.Version, out Decompiler.CSharp.LanguageVersion csharpLanguageVersion))
+ csharpLanguageVersion = Decompiler.CSharp.LanguageVersion.Latest;
+ decompilerSettings.SetLanguageVersion(csharpLanguageVersion);
+ if (decompilerSettings.ExtensionMembers && extInfo.InfoOfImplementationMember((IMethod)MethodDefinition.MemberDefinition).HasValue)
+ return FilterResult.Hidden;
+ }
if (settings.SearchTermMatches(MethodDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Language.ShowMember(MethodDefinition)))
return FilterResult.Match;
else
diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs
index b849e04072..34aa460311 100644
--- a/ILSpy/TreeNodes/PropertyTreeNode.cs
+++ b/ILSpy/TreeNodes/PropertyTreeNode.cs
@@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
+using System.Diagnostics;
using System.Reflection.Metadata;
using System.Windows.Media;
@@ -33,12 +34,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
///
public sealed class PropertyTreeNode : ILSpyTreeNode, IMemberTreeNode
{
- readonly bool isIndexer;
-
public PropertyTreeNode(IProperty property)
{
this.PropertyDefinition = property ?? throw new ArgumentNullException(nameof(property));
- this.isIndexer = property.IsIndexer;
if (property.CanGet)
this.Children.Add(new MethodTreeNode(property.Getter));
@@ -56,9 +54,10 @@ public PropertyTreeNode(IProperty property)
private IProperty GetPropertyDefinition()
{
- return ((MetadataModule)PropertyDefinition.ParentModule?.MetadataFile
+ var pd = ((MetadataModule)PropertyDefinition.ParentModule?.MetadataFile
?.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)
- ?.MainModule)?.GetDefinition((PropertyDefinitionHandle)PropertyDefinition.MetadataToken) ?? PropertyDefinition;
+ ?.MainModule)?.GetDefinition((PropertyDefinitionHandle)PropertyDefinition.MetadataToken);
+ return (IProperty)pd?.Specialize(PropertyDefinition.Substitution) ?? PropertyDefinition;
}
public static object GetText(IProperty property, Language language, bool includeDeclaringTypeName = false)
@@ -70,8 +69,11 @@ public static object GetText(IProperty property, Language language, bool include
public static ImageSource GetIcon(IProperty property)
{
+ IMethod accessor = property.Getter ?? property.Setter;
+ Debug.Assert(accessor != null, "Property must have at least one accessor");
+ bool isExtension = property.ResolveExtensionInfo()?.InfoOfExtensionMember((IMethod)accessor.MemberDefinition) != null;
return Images.GetIcon(property.IsIndexer ? MemberIcon.Indexer : MemberIcon.Property,
- Images.GetOverlayIcon(property.Accessibility), property.IsStatic);
+ Images.GetOverlayIcon(property.Accessibility), property.IsStatic, isExtension);
}
public override FilterResult Filter(LanguageSettings settings)
@@ -86,7 +88,10 @@ public override FilterResult Filter(LanguageSettings settings)
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
- language.DecompileProperty(PropertyDefinition, output, options);
+ if (Parent is ExtensionTreeNode && language is CSharpLanguage cs)
+ cs.DecompileExtension(PropertyDefinition, output, options);
+ else
+ language.DecompileProperty(PropertyDefinition, output, options);
}
public override bool IsPublicAPI {
diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs
index 5502cab46a..dc2252899c 100644
--- a/ILSpy/TreeNodes/TypeTreeNode.cs
+++ b/ILSpy/TreeNodes/TypeTreeNode.cs
@@ -94,6 +94,14 @@ protected override void LoadChildren()
this.Children.Add(new BaseTypesTreeNode(ParentAssemblyNode.LoadedAssembly.GetMetadataFileOrNull(), TypeDefinition));
if (!TypeDefinition.IsSealed)
this.Children.Add(new DerivedTypesTreeNode(ParentAssemblyNode.AssemblyList, TypeDefinition));
+ var extensionInfo = TypeDefinition.ExtensionInfo;
+ if (extensionInfo != null)
+ {
+ foreach (var extensionGroup in extensionInfo.ExtensionGroups)
+ {
+ this.Children.Add(new ExtensionTreeNode(TypeDefinition, extensionGroup, ParentAssemblyNode));
+ }
+ }
foreach (var nestedType in TypeDefinition.NestedTypes.OrderBy(t => t.Name, NaturalStringComparer.Instance))
{
this.Children.Add(new TypeTreeNode(nestedType, ParentAssemblyNode));
@@ -140,7 +148,7 @@ public override void Decompile(Language language, ITextOutput output, Decompilat
public static ImageSource GetIcon(ITypeDefinition type)
{
- return Images.GetIcon(GetTypeIcon(type, out bool isStatic), GetOverlayIcon(type), isStatic);
+ return Images.GetIcon(GetTypeIcon(type, out bool isStatic), GetOverlayIcon(type), isStatic, false);
}
internal static TypeIcon GetTypeIcon(IType type, out bool isStatic)
@@ -163,7 +171,7 @@ internal static TypeIcon GetTypeIcon(IType type, out bool isStatic)
}
}
- static AccessOverlayIcon GetOverlayIcon(ITypeDefinition type)
+ internal static AccessOverlayIcon GetOverlayIcon(ITypeDefinition type)
{
switch (type.Accessibility)
{