Browse Source

Merge pull request #3680 from icsharpcode/csharp14/extensions

Dedicated UI and Decompiler APIs for C# 14 extensions
pull/3687/head
Christoph Wille 2 weeks ago committed by GitHub
parent
commit
29bc5f204b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 2
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 110
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionEverything.cs
  4. 38
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs
  5. 232
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  6. 62
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
  7. 9
      ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs
  8. 15
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  9. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  10. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
  11. 4
      ICSharpCode.Decompiler/Output/IAmbience.cs
  12. 22
      ICSharpCode.Decompiler/Output/TextTokenWriter.cs
  13. 8
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  14. 123
      ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs
  15. 3
      ICSharpCode.Decompiler/TypeSystem/IMethod.cs
  16. 8
      ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
  17. 6
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
  18. 4
      ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs
  19. 12
      ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
  20. 4
      ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs
  21. 2
      ILSpy/ExtensionMethods.cs
  22. 59
      ILSpy/Images/Images.cs
  23. 1
      ILSpy/Images/MemberIcon.cs
  24. 51
      ILSpy/Images/OverlayExtension.svg
  25. 11
      ILSpy/Images/OverlayExtension.xaml
  26. 33
      ILSpy/Languages/CSharpLanguage.cs
  27. 2
      ILSpy/Metadata/CorTables/EventTableTreeNode.cs
  28. 2
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  29. 2
      ILSpy/TreeNodes/EventTreeNode.cs
  30. 107
      ILSpy/TreeNodes/ExtensionTreeNode.cs
  31. 8
      ILSpy/TreeNodes/FieldTreeNode.cs
  32. 7
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  33. 35
      ILSpy/TreeNodes/MethodTreeNode.cs
  34. 19
      ILSpy/TreeNodes/PropertyTreeNode.cs
  35. 12
      ILSpy/TreeNodes/TypeTreeNode.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -156,7 +156,7 @@ @@ -156,7 +156,7 @@
<Compile Include="TestCases\ILPretty\Issue3524.cs" />
<Compile Include="TestCases\ILPretty\Issue3552.cs" />
<Compile Include="TestCases\Pretty\ExpandParamsArgumentsDisabled.cs" />
<Compile Include="TestCases\Pretty\ExtensionProperties.cs" />
<Compile Include="TestCases\Pretty\ExtensionEverything.cs" />
<Compile Include="TestCases\Pretty\Issue3452.cs" />
<Compile Include="TestCases\Pretty\Issue3541.cs" />
<Compile Include="TestCases\Pretty\Issue3571_C.cs" />

2
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -560,7 +560,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -560,7 +560,7 @@ namespace ICSharpCode.Decompiler.Tests
}
[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);
}

110
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionEverything.cs

@ -0,0 +1,110 @@ @@ -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<T>(IEnumerable<T>)
{
}
extension<T>(IEnumerable<T> x)
{
}
extension<T>(IEnumerable<T> y)
{
}
extension<TKey, TValue>(Dictionary<TKey, TValue>)
{
}
extension<TKey, TValue>(Dictionary<TKey, TValue> x)
{
}
extension<TKey, TValue>(Dictionary<TKey, TValue> y)
{
}
}
internal static class ExtensionEverything
{
extension<T>(ICollection<T> 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<T2>(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<string> collection = new List<string>();
// 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<string>.StaticExtension();
}
}
}

38
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs

@ -1,38 +0,0 @@ @@ -1,38 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class ExtensionProperties
{
extension<T>(ICollection<T> 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<T2>(int index) where T2 : T
{
return (T2)(object)collection.ElementAt(index);
}
public static void StaticExtension()
{
}
}
}
}

232
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -274,7 +274,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -274,7 +274,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
/// <param name="module">The module containing the member.</param>
/// <param name="member">The metadata token/handle of the member. Can be a TypeDef, MethodDef or FieldDef.</param>
/// <param name="settings">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.</param>
/// <param name="settings">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.</param>
public static bool MemberIsHidden(MetadataFile module, EntityHandle member, DecompilerSettings settings)
{
if (module == null || member.IsNil)
@ -295,7 +295,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -295,7 +295,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (settings.LocalFunctions && LocalFunctionDecompiler.IsLocalFunctionMethod(module, methodHandle))
return true;
if (settings.AnonymousMethods && methodHandle.HasGeneratedName(metadata) && methodHandle.IsCompilerGenerated(metadata))
return true;
return name != "<Extension>$";
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedMainMethod(module, methodHandle))
return true;
return false;
@ -319,7 +319,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -319,7 +319,7 @@ namespace ICSharpCode.Decompiler.CSharp
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("<G>$", StringComparison.Ordinal)))
return true;
}
else if (type.IsCompilerGenerated(metadata))
@ -506,6 +506,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -506,6 +506,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.SupportUnsignedRightShift = settings.UnsignedRightShift;
typeSystemAstBuilder.SupportOperatorChecked = settings.CheckedOperators;
typeSystemAstBuilder.AlwaysUseGlobal = settings.AlwaysUseGlobal;
typeSystemAstBuilder.SupportExtensionDeclarations = settings.ExtensionMembers;
return typeSystemAstBuilder;
}
@ -726,6 +727,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -726,6 +727,11 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -911,6 +917,55 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
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, "<G>$"))
{
implementationPart = FindImplementations(metadata.GetTypeDefinition(containerHandle).GetMethods());
}
else if (metadata.StringComparer.StartsWith(declType.Name, "<M>$"))
{
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;
}
}
/// <summary>
/// Decompiles the whole module into a single string.
/// </summary>
@ -1019,6 +1074,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1019,6 +1074,7 @@ namespace ICSharpCode.Decompiler.CSharp
bool first = true;
ITypeDefinition parentTypeDef = null;
ExtensionInfo parentExtensionInfo = null;
foreach (var entity in definitions)
{
@ -1038,7 +1094,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1038,7 +1094,8 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1055,7 +1112,8 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1086,6 +1144,61 @@ namespace ICSharpCode.Decompiler.CSharp
return syntaxTree;
}
public SyntaxTree DecompileExtension(EntityHandle handle)
{
if (handle.IsNil)
throw new ArgumentNullException(nameof(handle));
syntaxTree = new SyntaxTree();
var namespaces = new HashSet<string>();
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1202,6 +1315,9 @@ namespace ICSharpCode.Decompiler.CSharp
/// <param name="member">The node of the member which new modifier state should be determined.</param>
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1278,7 +1394,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1288,15 +1404,33 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1327,32 +1461,10 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1478,7 +1590,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1549,31 +1661,45 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
}
}
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)

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

@ -58,6 +58,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -58,6 +58,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
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 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -129,11 +138,21 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
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<ParameterDeclaration> 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 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -213,18 +232,31 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
}
static bool HasParameters(ISymbol e)
bool IsExtension(ITypeDefinition? typeDef, out (IMethod Marker, IReadOnlyList<ITypeParameter> 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 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -246,6 +278,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
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 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -268,8 +301,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
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 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -371,15 +412,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
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<TypeParameterDeclaration> typeParameters, TokenWriter writer, CSharpFormattingOptions formattingPolicy)
{
if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList)
{
var outputVisitor = new CSharpOutputVisitor(writer, formattingPolicy);
IEnumerable<TypeParameterDeclaration> typeParameters = node.GetChildrenByRole(Roles.TypeParameter);
if ((ConversionFlags & ConversionFlags.ShowTypeParameterVarianceModifier) == 0)
{
typeParameters = typeParameters.Select(RemoveVarianceModifier);

9
ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs

@ -65,6 +65,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -65,6 +65,12 @@ namespace ICSharpCode.Decompiler.CSharp
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 @@ namespace ICSharpCode.Decompiler.CSharp @@ -76,6 +82,9 @@ namespace ICSharpCode.Decompiler.CSharp
foreach (var nestedType in td.NestedTypes)
{
bool isGroupOrMarker = extensionInfo?.IsExtensionGroupType(nestedType) == true || extensionInfo?.IsExtensionMarkerType(nestedType, out _) == true;
if (isGroupOrMarker)
continue;
CollectNamespaces(nestedType, module, mappingInfo);
}

15
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -252,6 +252,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -252,6 +252,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// Controls whether all fully qualified type names should be prefixed with "global::".
/// </summary>
public bool AlwaysUseGlobal { get; set; }
/// <summary>
/// Controls whether C# 14 "extension" declarations are supported.
/// </summary>
public bool SupportExtensionDeclarations { get; set; }
#endregion
#region Convert Type
@ -1812,6 +1817,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1812,6 +1817,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
public EntityDeclaration ConvertExtension((IMethod MarkerMethod, IReadOnlyList<ITypeParameter> 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;

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
<GenerateAssemblyInformationalVersionAttribute>False</GenerateAssemblyInformationalVersionAttribute>
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>13</LangVersion>
<LangVersion>14</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>ICSharpCode.Decompiler.snk</AssemblyOriginatorKeyFile>

2
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -53,7 +53,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -53,7 +53,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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; }

4
ICSharpCode.Decompiler/Output/IAmbience.cs

@ -121,6 +121,10 @@ namespace ICSharpCode.Decompiler.Output @@ -121,6 +121,10 @@ namespace ICSharpCode.Decompiler.Output
/// Support C# 7.2 <c>private protected</c>.
/// </summary>
UsePrivateProtectedAccessibility = 0x200000,
/// <summary>
/// Support C# 14 <c>extension</c> declarations.
/// </summary>
SupportExtensionDeclarations = 0x400000,
StandardConversionFlags = ShowParameterNames |
ShowAccessibility |

22
ICSharpCode.Decompiler/Output/TextTokenWriter.cs

@ -243,6 +243,22 @@ namespace ICSharpCode.Decompiler @@ -243,6 +243,22 @@ namespace ICSharpCode.Decompiler
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 @@ namespace ICSharpCode.Decompiler @@ -255,17 +271,15 @@ namespace ICSharpCode.Decompiler
}
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--;

8
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -150,12 +150,16 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -150,12 +150,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
FirstClassSpanTypes = 0x40000,
/// <summary>
/// If this option is active, extension member groups are detected, otherwise the compiler-generated nested classes are left as-is.
/// </summary>
ExtensionMembers = 0x80000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# language features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
| RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes
| RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes | ExtensionMembers
}
/// <summary>
@ -201,6 +205,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -201,6 +205,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.ParamsCollections;
if (settings.FirstClassSpanTypes)
typeSystemOptions |= TypeSystemOptions.FirstClassSpanTypes;
if (settings.ExtensionMembers)
typeSystemOptions |= TypeSystemOptions.ExtensionMembers;
return typeSystemOptions;
}

123
ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs

@ -25,6 +25,7 @@ using System.Linq; @@ -25,6 +25,7 @@ using System.Linq;
using System.Reflection.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.TypeSystem
{
@ -32,11 +33,14 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -32,11 +33,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
readonly Dictionary<IMember, ExtensionMemberInfo> extensionMemberMap;
readonly Dictionary<IMember, ExtensionMemberInfo> implementationMemberMap;
readonly List<(IMethod Marker, IReadOnlyList<ITypeParameter> 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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -86,7 +90,9 @@ namespace ICSharpCode.Decompiler.TypeSystem
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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -111,9 +117,19 @@ namespace ICSharpCode.Decompiler.TypeSystem
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<IMethod> 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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -133,25 +149,24 @@ namespace ICSharpCode.Decompiler.TypeSystem
extensionMethods.Add(method);
}
CollectImplementationMethods(extensionGroupsContainer, marker, extensionMethods, extensionGroupTypeParameters);
CollectImplementationMethods(marker, extensionGroupTypeParameters, extensionMethods);
}
return true;
}
void CollectImplementationMethods(ITypeDefinition extGroup, IMethod marker, List<IMethod> extensionMethods, ITypeParameter[] extensionGroupTypeParameters)
void CollectImplementationMethods(IMethod marker, IReadOnlyList<ITypeParameter> typeParameters, List<IMethod> 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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -200,48 +215,15 @@ namespace ICSharpCode.Decompiler.TypeSystem
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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -252,18 +234,51 @@ namespace ICSharpCode.Decompiler.TypeSystem
return this.implementationMemberMap.TryGetValue(method, out var value) ? value : null;
}
public IEnumerable<IGrouping<(IMethod Marker, ITypeParameter[] TypeParameters), ExtensionMemberInfo>> 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<ITypeParameter> 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<IMember> GetMembersOfGroup(IMethod marker)
{
return this.extensionMemberMap.Values.Any(x => x.ExtensionGroupingType.Equals(type));
return GetMembers().Distinct();
IEnumerable<IMember> 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<ITypeParameter> TypeParameters)> ExtensionGroups => this.extensionGroups;
}
public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation, ITypeParameter[] extensionGroupingTypeParameters)
public readonly struct ExtensionMemberInfo(IMethod marker, IReadOnlyList<ITypeParameter> typeParameters, IMethod extension, IMethod implementation)
{
/// <summary>
/// Metadata-only method called '&lt;Extension&gt;$'. Has the C# signature for the extension declaration.
@ -282,6 +297,11 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -282,6 +297,11 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
public readonly IMethod ImplementationMethod = implementation;
/// <summary>
/// This is the array of type parameters for the extension declaration.
/// </summary>
public readonly IReadOnlyList<ITypeParameter> ExtensionGroupingTypeParameters = typeParameters;
/// <summary>
/// This is the enclosing static class.
/// </summary>
@ -293,11 +313,6 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -293,11 +313,6 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
public ITypeDefinition ExtensionGroupingType => ExtensionMember.DeclaringTypeDefinition!;
/// <summary>
/// This is the array of type parameters for the extension declaration.
/// </summary>
public ITypeParameter[] ExtensionGroupingTypeParameters => extensionGroupingTypeParameters;
/// <summary>
/// This class holds the type parameters for the extension declaration with full fidelity of C# constraints.
/// </summary>

3
ICSharpCode.Decompiler/TypeSystem/IMethod.cs

@ -66,6 +66,9 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -66,6 +66,9 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
IReadOnlyList<IType> TypeArguments { get; }
/// <summary>
/// Returns true for classic extension methods, where extension method == implementation method, otherwise returns false.
/// </summary>
bool IsExtensionMethod { get; }
bool IsLocalFunction { get; }
bool IsConstructor { get; }

8
ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs

@ -28,7 +28,6 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -28,7 +28,6 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity
{
ExtensionInfo? ExtensionInfo { get; }
IReadOnlyList<ITypeDefinition> NestedTypes { get; }
IReadOnlyList<IMember> Members { get; }
@ -77,6 +76,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -77,6 +76,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// <remarks>This property is used to speed up the search for extension members.</remarks>
bool HasExtensions { get; }
/// <summary>
/// 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.
/// </summary>
ExtensionInfo? ExtensionInfo { get; }
/// <summary>
/// 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.

6
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs

@ -150,6 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -150,6 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
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 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -203,8 +205,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
var fieldList = new List<IField>(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));

4
ICSharpCode.Decompiler/TypeSystem/MetadataModule.cs

@ -274,7 +274,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -274,7 +274,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
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 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -291,7 +291,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
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)

12
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
@ -871,5 +872,16 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -871,5 +872,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
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;
}
}
}

4
ICSharpCode.ILSpyX/Abstractions/ITreeNode.cs

@ -24,8 +24,8 @@ namespace ICSharpCode.ILSpyX.Abstractions @@ -24,8 +24,8 @@ namespace ICSharpCode.ILSpyX.Abstractions
{
public interface ITreeNode
{
object Text { get; }
object Icon { get; }
object? Text { get; }
object? Icon { get; }
IEnumerable<ITreeNode> Children { get; }
void EnsureLazyChildren();

2
ILSpy/ExtensionMethods.cs

@ -76,7 +76,7 @@ namespace ICSharpCode.ILSpy @@ -76,7 +76,7 @@ namespace ICSharpCode.ILSpy
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))

59
ILSpy/Images/Images.cs

@ -119,6 +119,7 @@ namespace ICSharpCode.ILSpy @@ -119,6 +119,7 @@ namespace ICSharpCode.ILSpy
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 @@ namespace ICSharpCode.ILSpy @@ -203,16 +204,16 @@ namespace ICSharpCode.ILSpy
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 @@ namespace ICSharpCode.ILSpy @@ -236,37 +237,45 @@ namespace ICSharpCode.ILSpy
}
}
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 @@ namespace ICSharpCode.ILSpy @@ -330,7 +339,6 @@ namespace ICSharpCode.ILSpy
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 @@ namespace ICSharpCode.ILSpy @@ -370,9 +378,6 @@ namespace ICSharpCode.ILSpy
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 @@ namespace ICSharpCode.ILSpy @@ -389,35 +394,35 @@ namespace ICSharpCode.ILSpy
private abstract class IconCache<T>
{
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);

1
ILSpy/Images/MemberIcon.cs

@ -30,7 +30,6 @@ namespace ICSharpCode.ILSpy @@ -30,7 +30,6 @@ namespace ICSharpCode.ILSpy
Constructor,
VirtualMethod,
Operator,
ExtensionMethod,
PInvokeMethod,
Event
}

51
ILSpy/Images/OverlayExtension.svg

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
version="1.1"
id="svg2"
sodipodi:docname="ExtensionOverlay.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="72.9375"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="373"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<style
type="text/css"
id="style1">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;} .icon-vs-fg{fill:#F0EFF1;} .icon-vs-action-purple{fill:#652D90;}</style>
<path
class="icon-canvas-transparent"
d="M 16,16 H 0 V 0 h 16 z"
id="canvas"
style="display:inline" />
<path
class="icon-vs-out"
d="M 14.47,9.056 14,9.525 V 5 H 10 V 9.525 L 9.53,9.055 8,10.586 v 1.889 L 11.525,16 h 0.949 L 16,12.475 v -1.889 z"
id="outline"
style="display:inline"
sodipodi:nodetypes="ccccccccccccc" />
<path
class="icon-vs-bg"
d="M 13,9 H 11 V 8 h 2 z m 0,-3 h -2 v 1 h 2 z M 14.47,10.47 13,11.939 V 10 h -2 v 1.939 L 9.53,10.469 8.47,11.53 12,15.06 15.53,11.53 Z"
id="path2" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

11
ILSpy/Images/OverlayExtension.xaml

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
<DrawingGroup xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<DrawingGroup.Children>
<GeometryDrawing Geometry="F1 M16,16z M0,0z M16,16L0,16 0,0 16,0z">
<GeometryDrawing.Brush>
<SolidColorBrush Color="#FFF6F6F6" Opacity="0" />
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1 M16,16z M0,0z M14.47,9.056L14,9.525 14,5 10,5 10,9.525 9.53,9.055 8,10.586 8,12.475 11.525,16 12.474,16 16,12.475 16,10.586z" />
<GeometryDrawing Brush="#FF424242" Geometry="F1 M16,16z M0,0z M13,9L11,9 11,8 13,8z M13,6L11,6 11,7 13,7z M14.47,10.47L13,11.939 13,10 11,10 11,11.939 9.53,10.469 8.47,11.53 12,15.06 15.53,11.53z" />
</DrawingGroup.Children>
</DrawingGroup>

33
ILSpy/Languages/CSharpLanguage.cs

@ -364,6 +364,39 @@ namespace ICSharpCode.ILSpy @@ -364,6 +364,39 @@ namespace ICSharpCode.ILSpy
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);

2
ILSpy/Metadata/CorTables/EventTableTreeNode.cs

@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy.Metadata @@ -71,7 +71,7 @@ namespace ICSharpCode.ILSpy.Metadata
IEntity IMemberTreeNode.Member {
get {
return ((MetadataModule)metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion)?.MainModule)?.GetDefinition(handle);
return ((MetadataModule)GetTypeSystemWithCurrentOptionsOrNull(metadataFile)?.MainModule)?.GetDefinition(handle);
}
}

2
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -259,7 +259,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -259,7 +259,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
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();

2
ILSpy/TreeNodes/EventTreeNode.cs

@ -68,7 +68,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -68,7 +68,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
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)

107
ILSpy/TreeNodes/ExtensionTreeNode.cs

@ -0,0 +1,107 @@ @@ -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<ITypeParameter> 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<ITypeParameter> 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<IProperty>().OrderBy(p => p.Name, NaturalStringComparer.Instance))
{
this.Children.Add(new PropertyTreeNode(property));
}
foreach (var method in members.OfType<IMethod>().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);
}
}
}

8
ILSpy/TreeNodes/FieldTreeNode.cs

@ -61,15 +61,15 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -61,15 +61,15 @@ namespace ICSharpCode.ILSpy.TreeNodes
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)

7
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -16,6 +16,8 @@ @@ -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.Reflection.Metadata.Ecma335; @@ -25,6 +27,7 @@ using System.Reflection.Metadata.Ecma335;
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -48,6 +51,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
public Language Language => LanguageService.Language;
protected static ICompilation? GetTypeSystemWithCurrentOptionsOrNull(MetadataFile metadataFile) => metadataFile.GetTypeSystemWithCurrentOptionsOrNull(SettingsService, AssemblyTreeModel.CurrentLanguageVersion);
protected static AssemblyTreeModel AssemblyTreeModel { get; } = App.ExportProvider.GetExportedValue<AssemblyTreeModel>();
protected static ICollection<IResourceNodeFactory> ResourceNodeFactories { get; } = App.ExportProvider.GetExportedValues<IResourceNodeFactory>().ToArray();
@ -133,7 +138,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -133,7 +138,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
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;

35
ILSpy/TreeNodes/MethodTreeNode.cs

@ -46,9 +46,10 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -46,9 +46,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -60,31 +61,47 @@ namespace ICSharpCode.ILSpy.TreeNodes
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

19
ILSpy/TreeNodes/PropertyTreeNode.cs

@ -17,6 +17,7 @@ @@ -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 @@ -33,12 +34,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
/// </summary>
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -56,9 +54,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -70,8 +69,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -86,7 +88,10 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 {

12
ILSpy/TreeNodes/TypeTreeNode.cs

@ -94,6 +94,14 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -94,6 +94,14 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -140,7 +148,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
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 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -163,7 +171,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
}
}
static AccessOverlayIcon GetOverlayIcon(ITypeDefinition type)
internal static AccessOverlayIcon GetOverlayIcon(ITypeDefinition type)
{
switch (type.Accessibility)
{

Loading…
Cancel
Save