Browse Source

Merge pull request #3530 from icsharpcode/csharp14/extensions

C# 14 extension members: Add initial support
pull/3532/head
Siegfried Pammer 5 months ago committed by GitHub
parent
commit
e3b1eb8a97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .github/workflows/build-ilspy.yml
  2. 5
      BuildTools/pre-commit
  3. 3
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  5. 38
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExtensionProperties.cs
  6. 20
      ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs
  7. 20
      ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs
  8. 96
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  9. 3
      ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
  10. 30
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  11. 2
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  12. 15
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  13. 4
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  14. 70
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs
  15. 24
      ICSharpCode.Decompiler/DecompilerSettings.cs
  16. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  17. 176
      ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs
  18. 7
      ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
  19. 21
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
  20. 3
      ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs
  21. 2
      ICSharpCode.ILSpyX/Analyzers/Builtin/TypeExtensionMethodsAnalyzer.cs
  22. 1
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  23. 1
      ILSpy/Languages/CSharpLanguage.cs

5
.github/workflows/build-ilspy.yml

@ -44,7 +44,10 @@ jobs:
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
- name: Install dotnet-format - name: Install dotnet-format
run: dotnet tool install -g dotnet-format --version "9.0.520307" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json env:
DOTNET_FORMAT_VERSION: 10.0.100-preview.6.25358.103
DOTNET_FORMAT_SOURCE: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-transport/nuget/v3/index.json
run: dotnet tool install -g dotnet-format --version "${{env.DOTNET_FORMAT_VERSION}}" --add-source "${{env.DOTNET_FORMAT_SOURCE}}"
- name: Install wix (locked version) - name: Install wix (locked version)
run: dotnet tool install --global wix --version 6.0.0 run: dotnet tool install --global wix --version 6.0.0

5
BuildTools/pre-commit

@ -5,11 +5,12 @@
set -eu set -eu
DOTNET_FORMAT_VERSION=9.0.520307 DOTNET_FORMAT_VERSION=10.0.100-preview.6.25358.103
DOTNET_FORMAT_SOURCE="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-transport/nuget/v3/index.json"
DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION" DOTNET_PATH="$LOCALAPPDATA/ICSharpCode/ILSpy/dotnet-format-$DOTNET_FORMAT_VERSION"
if [ ! -d "$DOTNET_PATH" ]; then if [ ! -d "$DOTNET_PATH" ]; then
echo "Downloading dotnet-format $DOTNET_FORMAT_VERSION..." echo "Downloading dotnet-format $DOTNET_FORMAT_VERSION..."
dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" dotnet tool install --tool-path "$DOTNET_PATH" dotnet-format --version "$DOTNET_FORMAT_VERSION" --add-source "$DOTNET_FORMAT_SOURCE"
fi fi
"$DOTNET_PATH/dotnet-format.exe" --version "$DOTNET_PATH/dotnet-format.exe" --version

3
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework> <TargetFramework>net10.0-windows</TargetFramework>
<LangVersion>13</LangVersion> <LangVersion>preview</LangVersion>
<RuntimeIdentifier Condition="$(IsWindowsX64) == true">win-x64</RuntimeIdentifier> <RuntimeIdentifier Condition="$(IsWindowsX64) == true">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="$(IsWindowsARM64) == true">win-arm64</RuntimeIdentifier> <RuntimeIdentifier Condition="$(IsWindowsARM64) == true">win-arm64</RuntimeIdentifier>
@ -146,6 +146,7 @@
<Compile Include="TestCases\ILPretty\Issue3442.cs" /> <Compile Include="TestCases\ILPretty\Issue3442.cs" />
<Compile Include="TestCases\ILPretty\Issue3466.cs" /> <Compile Include="TestCases\ILPretty\Issue3466.cs" />
<Compile Include="TestCases\ILPretty\Issue3524.cs" /> <Compile Include="TestCases\ILPretty\Issue3524.cs" />
<Compile Include="TestCases\Pretty\ExtensionProperties.cs" />
<None Include="TestCases\ILPretty\Issue3504.cs" /> <None Include="TestCases\ILPretty\Issue3504.cs" />
<Compile Include="TestCases\ILPretty\MonoFixed.cs" /> <Compile Include="TestCases\ILPretty\MonoFixed.cs" />
<Compile Include="TestCases\Pretty\Comparisons.cs" /> <Compile Include="TestCases\Pretty\Comparisons.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -527,6 +527,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable);
} }
[Test]
public async Task ExtensionProperties([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
}
[Test] [Test]
public async Task NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) public async Task NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{ {

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

@ -0,0 +1,38 @@
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()
{
}
}
}
}

20
ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs

@ -1496,7 +1496,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem
Assert.That(method.IsExtensionMethod); Assert.That(method.IsExtensionMethod);
Assert.That(method.ReducedFrom, Is.Null); Assert.That(method.ReducedFrom, Is.Null);
Assert.That(type.HasExtensionMethods); Assert.That(type.HasExtensions);
} }
[Test] [Test]
@ -1993,5 +1993,23 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem
Assert.That(@class.HasAttribute(KnownAttribute.SpecialName)); Assert.That(@class.HasAttribute(KnownAttribute.SpecialName));
Assert.That(@struct.HasAttribute(KnownAttribute.SpecialName)); Assert.That(@struct.HasAttribute(KnownAttribute.SpecialName));
} }
[Test]
public void ExtensionEverything()
{
var extensionEverything = GetTypeDefinition(typeof(ExtensionEverything));
Assert.That(extensionEverything.IsStatic, Is.True, "ExtensionEverything should be static");
Assert.That(extensionEverything.HasExtensions, Is.True, "ExtensionEverything should have extensions");
var info = extensionEverything.ExtensionInfo;
Assert.That(info, Is.Not.Null, "ExtensionEverything should have ExtensionInfo");
foreach (var method in extensionEverything.Methods)
{
Assert.That(method.IsStatic, Is.True, "Method should be static: " + method.Name);
ExtensionMemberInfo? infoOfImpl = info.InfoOfImplementationMember(method);
Assert.That(infoOfImpl, Is.Not.Null, "Method should have implementation info: " + method.Name);
ExtensionMemberInfo? infoOfExtension = info.InfoOfExtensionMember(infoOfImpl.Value.ExtensionMember);
Assert.That(infoOfExtension, Is.EqualTo(infoOfImpl), "Info of extension member should be equal to info of implementation member: " + method.Name);
}
}
} }
} }

20
ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemTestCase.cs

@ -753,4 +753,24 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem
[DispId(11)] [DispId(11)]
void StopRouter(); void StopRouter();
} }
public static class ExtensionEverything
{
extension(int input)
{
public void Method() { }
public void Method(char c) { }
public string AsString => input.ToString();
public string Test {
get => "Test";
set { }
}
public static void StaticMethod() { }
public static void StaticMethod(double x) { }
public static string StaticProperty => "StaticProperty";
public static void GenericMethod<T>(T value)
{
}
}
}
} }

96
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -318,7 +318,9 @@ namespace ICSharpCode.Decompiler.CSharp
return true; return true;
if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer")) if (settings.FixedBuffers && name.StartsWith("<", StringComparison.Ordinal) && name.Contains("__FixedBuffer"))
return true; return true;
if (settings.InlineArrays && name.StartsWith("<>y__InlineArray", StringComparison.Ordinal) && name.EndsWith("`1")) if (settings.InlineArrays && name.StartsWith("<>y__InlineArray", StringComparison.Ordinal) && name.EndsWith("`1", StringComparison.Ordinal))
return true;
if (settings.ExtensionMembers && name.StartsWith("<>E__", StringComparison.Ordinal))
return true; return true;
} }
else if (type.IsCompilerGenerated(metadata)) else if (type.IsCompilerGenerated(metadata))
@ -1037,7 +1039,7 @@ namespace ICSharpCode.Decompiler.CSharp
break; break;
case HandleKind.MethodDefinition: case HandleKind.MethodDefinition:
IMethod method = module.GetDefinition((MethodDefinitionHandle)entity); IMethod method = module.GetDefinition((MethodDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method))); syntaxTree.Members.Add(DoDecompile(method, decompileRun, new SimpleTypeResolveContext(method), null));
if (first) if (first)
{ {
parentTypeDef = method.DeclaringTypeDefinition; parentTypeDef = method.DeclaringTypeDefinition;
@ -1054,7 +1056,7 @@ namespace ICSharpCode.Decompiler.CSharp
break; break;
case HandleKind.PropertyDefinition: case HandleKind.PropertyDefinition:
IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity); IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity);
syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property))); syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), null));
if (first) if (first)
{ {
parentTypeDef = property.DeclaringTypeDefinition; parentTypeDef = property.DeclaringTypeDefinition;
@ -1361,6 +1363,36 @@ namespace ICSharpCode.Decompiler.CSharp
partialTypeInfo = null; partialTypeInfo = null;
} }
if (settings.ExtensionMembers)
{
foreach (var group in typeDef.ExtensionInfo?.GetGroups() ?? [])
{
var ext = new ExtensionDeclaration();
ext.TypeParameters.AddRange(group.Key.DeclaringTypeDefinition.TypeParameters.Select(tp => typeSystemAstBuilder.ConvertTypeParameter(tp)));
ext.ReceiverParameters.Add(typeSystemAstBuilder.ConvertParameter(group.Key.Parameters.Single()));
ext.Constraints.AddRange(group.Key.DeclaringTypeDefinition.TypeParameters.Select(c => typeSystemAstBuilder.ConvertTypeParameterConstraint(c)));
foreach (var member in group)
{
IMember extMember = member.ExtensionMember;
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);
}
typeDecl.Members.Add(ext);
}
}
// Decompile members that are not compiler-generated. // Decompile members that are not compiler-generated.
foreach (var entity in allOrderedEntities) foreach (var entity in allOrderedEntities)
{ {
@ -1368,7 +1400,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
continue; continue;
} }
DoDecompileMember(entity, recordDecompiler, partialTypeInfo); DoDecompileMember(entity, recordDecompiler, partialTypeInfo, typeDef.ExtensionInfo);
} }
// Decompile compiler-generated members that are still needed. // Decompile compiler-generated members that are still needed.
@ -1380,7 +1412,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Member is already decompiled. // Member is already decompiled.
continue; continue;
} }
DoDecompileMember(entity, recordDecompiler, partialTypeInfo); DoDecompileMember(entity, recordDecompiler, partialTypeInfo, typeDef.ExtensionInfo);
} }
// Add all decompiled members to syntax tree in the correct order. // Add all decompiled members to syntax tree in the correct order.
@ -1470,13 +1502,18 @@ namespace ICSharpCode.Decompiler.CSharp
Instrumentation.DecompilerEventSource.Log.DoDecompileTypeDefinition(typeDef.FullName, watch.ElapsedMilliseconds); Instrumentation.DecompilerEventSource.Log.DoDecompileTypeDefinition(typeDef.FullName, watch.ElapsedMilliseconds);
} }
void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler, PartialTypeInfo partialType) void DoDecompileMember(IEntity entity, RecordDecompiler recordDecompiler, PartialTypeInfo partialType, ExtensionInfo extensionInfo)
{ {
if (partialType != null && partialType.IsDeclaredMember(entity.MetadataToken)) if (partialType != null && partialType.IsDeclaredMember(entity.MetadataToken))
{ {
return; return;
} }
if (settings.ExtensionMembers && extensionInfo != null && entity is IMethod m && extensionInfo.InfoOfImplementationMember(m).HasValue)
{
return;
}
EntityDeclaration entityDecl; EntityDeclaration entityDecl;
switch (entity) switch (entity)
{ {
@ -1497,7 +1534,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
return; return;
} }
entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property)); entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property), null);
entityMap.Add(property, entityDecl); entityMap.Add(property, entityDecl);
break; break;
case IMethod method: case IMethod method:
@ -1505,7 +1542,7 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
return; return;
} }
entityDecl = DoDecompile(method, decompileRun, decompilationContext.WithCurrentMember(method)); entityDecl = DoDecompile(method, decompileRun, decompilationContext.WithCurrentMember(method), null);
entityMap.Add(method, entityDecl); entityMap.Add(method, entityDecl);
foreach (var helper in AddInterfaceImplHelpers(entityDecl, method, typeSystemAstBuilder)) foreach (var helper in AddInterfaceImplHelpers(entityDecl, method, typeSystemAstBuilder))
{ {
@ -1543,6 +1580,19 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
private EntityDeclaration DoDecompileExtensionMember(IMember extMember, ExtensionInfo info, DecompileRun decompileRun, ITypeResolveContext decompilationContext)
{
switch (extMember)
{
case IProperty p:
return DoDecompile(p, decompileRun, decompilationContext.WithCurrentMember(p), info);
case IMethod m:
return DoDecompile(m, decompileRun, decompilationContext.WithCurrentMember(m), info);
}
throw new NotSupportedException($"Extension member {extMember} is not supported for decompilation.");
}
EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, MetadataFile module) EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, MetadataFile module)
{ {
if (typeDef.HasAttribute(KnownAttribute.Flags)) if (typeDef.HasAttribute(KnownAttribute.Flags))
@ -1604,7 +1654,7 @@ namespace ICSharpCode.Decompiler.CSharp
return firstValue == 0 ? EnumValueDisplayMode.None : EnumValueDisplayMode.FirstOnly; return firstValue == 0 ? EnumValueDisplayMode.None : EnumValueDisplayMode.FirstOnly;
} }
EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeResolveContext decompilationContext) EntityDeclaration DoDecompile(IMethod method, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo extensionInfo)
{ {
Debug.Assert(decompilationContext.CurrentMember == method); Debug.Assert(decompilationContext.CurrentMember == method);
var watch = System.Diagnostics.Stopwatch.StartNew(); var watch = System.Diagnostics.Stopwatch.StartNew();
@ -1630,7 +1680,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
if (methodDefinition.HasBody()) if (methodDefinition.HasBody())
{ {
DecompileBody(method, methodDecl, decompileRun, decompilationContext); DecompileBody(method, methodDecl, decompileRun, decompilationContext, extensionInfo);
} }
else if (!method.IsAbstract && method.DeclaringType.Kind != TypeKind.Interface) else if (!method.IsAbstract && method.DeclaringType.Kind != TypeKind.Interface)
{ {
@ -1699,7 +1749,7 @@ namespace ICSharpCode.Decompiler.CSharp
return method.ReturnType.Kind == TypeKind.Void && method.Name == "InitializeComponent" && method.DeclaringTypeDefinition.GetNonInterfaceBaseTypes().Any(t => t.FullName == "System.Windows.Forms.Control"); return method.ReturnType.Kind == TypeKind.Void && method.Name == "InitializeComponent" && method.DeclaringTypeDefinition.GetNonInterfaceBaseTypes().Any(t => t.FullName == "System.Windows.Forms.Control");
} }
void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun decompileRun, ITypeResolveContext decompilationContext) void DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo extensionInfo)
{ {
try try
{ {
@ -1708,6 +1758,14 @@ namespace ICSharpCode.Decompiler.CSharp
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation, UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider DebugInfo = DebugInfoProvider
}; };
int parameterOffset = 0;
if (extensionInfo != null)
{
if (!method.IsStatic)
parameterOffset = 1; // implementation method has an additional receiver parameter
method = extensionInfo.InfoOfExtensionMember(method).Value.ImplementationMethod;
}
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken); var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);
var body = BlockStatement.Null; var body = BlockStatement.Null;
MethodBodyBlock methodBody; MethodBodyBlock methodBody;
@ -1727,7 +1785,7 @@ namespace ICSharpCode.Decompiler.CSharp
var function = ilReader.ReadIL((MethodDefinitionHandle)method.MetadataToken, methodBody, cancellationToken: CancellationToken); var function = ilReader.ReadIL((MethodDefinitionHandle)method.MetadataToken, methodBody, cancellationToken: CancellationToken);
function.CheckInvariant(ILPhase.Normal); function.CheckInvariant(ILPhase.Normal);
AddAnnotationsToDeclaration(method, entityDecl, function); AddAnnotationsToDeclaration(method, entityDecl, function, parameterOffset);
var localSettings = settings.Clone(); var localSettings = settings.Clone();
if (IsWindowsFormsInitializeComponentMethod(method)) if (IsWindowsFormsInitializeComponentMethod(method))
@ -1786,9 +1844,9 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
internal static void AddAnnotationsToDeclaration(IMethod method, EntityDeclaration entityDecl, ILFunction function) internal static void AddAnnotationsToDeclaration(IMethod method, EntityDeclaration entityDecl, ILFunction function, int parameterOffset = 0)
{ {
int i = 0; int i = parameterOffset;
var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); var parameters = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index);
foreach (var parameter in entityDecl.GetChildrenByRole(Roles.Parameter)) foreach (var parameter in entityDecl.GetChildrenByRole(Roles.Parameter))
{ {
@ -2023,7 +2081,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
} }
EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext) EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo extensionInfo)
{ {
Debug.Assert(decompilationContext.CurrentMember == property); Debug.Assert(decompilationContext.CurrentMember == property);
var watch = System.Diagnostics.Stopwatch.StartNew(); var watch = System.Diagnostics.Stopwatch.StartNew();
@ -2053,11 +2111,11 @@ namespace ICSharpCode.Decompiler.CSharp
bool setterHasBody = property.CanSet && property.Setter.HasBody; bool setterHasBody = property.CanSet && property.Setter.HasBody;
if (getterHasBody) if (getterHasBody)
{ {
DecompileBody(property.Getter, getter, decompileRun, decompilationContext); DecompileBody(property.Getter, getter, decompileRun, decompilationContext, extensionInfo);
} }
if (setterHasBody) if (setterHasBody)
{ {
DecompileBody(property.Setter, setter, decompileRun, decompilationContext); DecompileBody(property.Setter, setter, decompileRun, decompilationContext, extensionInfo);
} }
if (!getterHasBody && !setterHasBody && !property.IsAbstract && property.DeclaringType.Kind != TypeKind.Interface) if (!getterHasBody && !setterHasBody && !property.IsAbstract && property.DeclaringType.Kind != TypeKind.Interface)
{ {
@ -2113,11 +2171,11 @@ namespace ICSharpCode.Decompiler.CSharp
} }
if (adderHasBody) if (adderHasBody)
{ {
DecompileBody(ev.AddAccessor, ((CustomEventDeclaration)eventDecl).AddAccessor, decompileRun, decompilationContext); DecompileBody(ev.AddAccessor, ((CustomEventDeclaration)eventDecl).AddAccessor, decompileRun, decompilationContext, null);
} }
if (removerHasBody) if (removerHasBody)
{ {
DecompileBody(ev.RemoveAccessor, ((CustomEventDeclaration)eventDecl).RemoveAccessor, decompileRun, decompilationContext); DecompileBody(ev.RemoveAccessor, ((CustomEventDeclaration)eventDecl).RemoveAccessor, decompileRun, decompilationContext, null);
} }
if (!adderHasBody && !removerHasBody && !ev.IsAbstract && ev.DeclaringType.Kind != TypeKind.Interface) if (!adderHasBody && !removerHasBody && !ev.IsAbstract && ev.DeclaringType.Kind != TypeKind.Interface)
{ {

3
ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs

@ -36,7 +36,8 @@ namespace ICSharpCode.Decompiler.CSharp
CSharp11_0 = 1100, CSharp11_0 = 1100,
CSharp12_0 = 1200, CSharp12_0 = 1200,
CSharp13_0 = 1300, CSharp13_0 = 1300,
Preview = 1300, CSharp14_0 = 1400,
Preview = 1400,
Latest = 0x7FFFFFFF Latest = 0x7FFFFFFF
} }
} }

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

@ -2417,6 +2417,36 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(enumMemberDeclaration); EndNode(enumMemberDeclaration);
} }
public virtual void VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration)
{
StartNode(extensionDeclaration);
WriteAttributes(extensionDeclaration.Attributes);
WriteModifiers(extensionDeclaration.ModifierTokens);
WriteKeyword(ExtensionDeclaration.ExtensionKeywordRole);
WriteTypeParameters(extensionDeclaration.TypeParameters);
Space(policy.SpaceBeforeMethodDeclarationParentheses);
WriteCommaSeparatedListInParenthesis(extensionDeclaration.ReceiverParameters, policy.SpaceWithinMethodDeclarationParentheses);
foreach (Constraint constraint in extensionDeclaration.Constraints)
{
constraint.AcceptVisitor(this);
}
OpenBrace(policy.ClassBraceStyle);
bool first = true;
foreach (var member in extensionDeclaration.Members)
{
if (!first)
{
for (int i = 0; i < policy.MinimumBlankLinesBetweenMembers; i++)
NewLine();
}
first = false;
member.AcceptVisitor(this);
}
CloseBrace(policy.ClassBraceStyle);
NewLine();
EndNode(extensionDeclaration);
}
public virtual void VisitEventDeclaration(EventDeclaration eventDeclaration) public virtual void VisitEventDeclaration(EventDeclaration eventDeclaration)
{ {
StartNode(eventDeclaration); StartNode(eventDeclaration);

2
ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

@ -2215,7 +2215,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// TODO: maybe make this a property on INamespace? // TODO: maybe make this a property on INamespace?
return return
from c in ns.Types from c in ns.Types
where c.IsStatic && c.HasExtensionMethods && c.TypeParameters.Count == 0 && lookup.IsAccessible(c, false) where c.IsStatic && c.HasExtensions && c.TypeParameters.Count == 0 && lookup.IsAccessible(c, false)
from m in c.Methods from m in c.Methods
where m.IsExtensionMethod where m.IsExtensionMethod
select m; select m;

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

@ -157,6 +157,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren(enumMemberDeclaration); VisitChildren(enumMemberDeclaration);
} }
public virtual void VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration)
{
VisitChildren(extensionDeclaration);
}
public virtual void VisitUsingDeclaration(UsingDeclaration usingDeclaration) public virtual void VisitUsingDeclaration(UsingDeclaration usingDeclaration)
{ {
VisitChildren(usingDeclaration); VisitChildren(usingDeclaration);
@ -840,6 +845,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(enumMemberDeclaration); return VisitChildren(enumMemberDeclaration);
} }
public virtual T VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration)
{
return VisitChildren(extensionDeclaration);
}
public virtual T VisitUsingDeclaration(UsingDeclaration usingDeclaration) public virtual T VisitUsingDeclaration(UsingDeclaration usingDeclaration)
{ {
return VisitChildren(usingDeclaration); return VisitChildren(usingDeclaration);
@ -1523,6 +1533,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(enumMemberDeclaration, data); return VisitChildren(enumMemberDeclaration, data);
} }
public virtual S VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration, T data)
{
return VisitChildren(extensionDeclaration, data);
}
public virtual S VisitUsingDeclaration(UsingDeclaration usingDeclaration, T data) public virtual S VisitUsingDeclaration(UsingDeclaration usingDeclaration, T data)
{ {
return VisitChildren(usingDeclaration, data); return VisitChildren(usingDeclaration, data);

4
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
namespace ICSharpCode.Decompiler.CSharp.Syntax namespace ICSharpCode.Decompiler.CSharp.Syntax
{ {
/// <summary> /// <summary>
@ -136,6 +135,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitVariableInitializer(VariableInitializer variableInitializer); void VisitVariableInitializer(VariableInitializer variableInitializer);
void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration); void VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration);
void VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer); void VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer);
void VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration);
void VisitSyntaxTree(SyntaxTree syntaxTree); void VisitSyntaxTree(SyntaxTree syntaxTree);
void VisitSimpleType(SimpleType simpleType); void VisitSimpleType(SimpleType simpleType);
@ -285,6 +285,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitVariableInitializer(VariableInitializer variableInitializer); S VisitVariableInitializer(VariableInitializer variableInitializer);
S VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration); S VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration);
S VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer); S VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer);
S VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration);
S VisitSyntaxTree(SyntaxTree syntaxTree); S VisitSyntaxTree(SyntaxTree syntaxTree);
S VisitSimpleType(SimpleType simpleType); S VisitSimpleType(SimpleType simpleType);
@ -434,6 +435,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitVariableInitializer(VariableInitializer variableInitializer, T data); S VisitVariableInitializer(VariableInitializer variableInitializer, T data);
S VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration, T data); S VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration, T data);
S VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer, T data); S VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer, T data);
S VisitExtensionDeclaration(ExtensionDeclaration extensionDeclaration, T data);
S VisitSyntaxTree(SyntaxTree syntaxTree, T data); S VisitSyntaxTree(SyntaxTree syntaxTree, T data);
S VisitSimpleType(SimpleType simpleType, T data); S VisitSimpleType(SimpleType simpleType, T data);

70
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ExtensionDeclaration.cs

@ -0,0 +1,70 @@
// Copyright (c) 2025 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 ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public class ExtensionDeclaration : EntityDeclaration
{
public readonly static TokenRole ExtensionKeywordRole = new TokenRole("extension");
public override SymbolKind SymbolKind => throw new System.NotImplementedException();
public AstNodeCollection<TypeParameterDeclaration> TypeParameters {
get { return GetChildrenByRole(Roles.TypeParameter); }
}
public AstNodeCollection<ParameterDeclaration> ReceiverParameters {
get { return GetChildrenByRole(Roles.Parameter); }
}
public AstNodeCollection<Constraint> Constraints {
get { return GetChildrenByRole(Roles.Constraint); }
}
public AstNodeCollection<EntityDeclaration> Members {
get { return GetChildrenByRole(Roles.TypeMemberRole); }
}
public ExtensionDeclaration()
{
}
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitExtensionDeclaration(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitExtensionDeclaration(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitExtensionDeclaration(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
var o = other as ExtensionDeclaration;
return o != null;
}
}
}

24
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -171,10 +171,16 @@ namespace ICSharpCode.Decompiler
{ {
paramsCollections = false; paramsCollections = false;
} }
if (languageVersion < CSharp.LanguageVersion.CSharp14_0)
{
extensionMembers = false;
}
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (extensionMembers)
return CSharp.LanguageVersion.CSharp14_0;
if (paramsCollections) if (paramsCollections)
return CSharp.LanguageVersion.CSharp13_0; return CSharp.LanguageVersion.CSharp13_0;
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays) if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays)
@ -2117,6 +2123,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool extensionMembers = true;
/// <summary>
/// Gets/Sets whether C# 14.0 extension members should be transformed.
/// </summary>
[Category("C# 14.0 / VS 202x.yy")]
[Description("DecompilerSettings.ExtensionMembers")]
public bool ExtensionMembers {
get { return extensionMembers; }
set {
if (extensionMembers != value)
{
extensionMembers = value;
OnPropertyChanged();
}
}
}
bool separateLocalVariableDeclarations = false; bool separateLocalVariableDeclarations = false;
/// <summary> /// <summary>

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -104,6 +104,7 @@
<Compile Include="CSharp\CallBuilder.cs" /> <Compile Include="CSharp\CallBuilder.cs" />
<Compile Include="CSharp\CSharpLanguageVersion.cs" /> <Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\Syntax\Expressions\RecursivePatternExpression.cs" /> <Compile Include="CSharp\Syntax\Expressions\RecursivePatternExpression.cs" />
<Compile Include="CSharp\Syntax\TypeMembers\ExtensionDeclaration.cs" />
<Compile Include="DecompilationProgress.cs" /> <Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" /> <Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" /> <Compile Include="Disassembler\SortByNameProcessor.cs" />
@ -155,6 +156,7 @@
<Compile Include="Metadata\MetadataGenericContext.cs" /> <Compile Include="Metadata\MetadataGenericContext.cs" />
<Compile Include="Metadata\ReferenceLoadInfo.cs" /> <Compile Include="Metadata\ReferenceLoadInfo.cs" />
<Compile Include="Properties\DecompilerVersionInfo.cs" /> <Compile Include="Properties\DecompilerVersionInfo.cs" />
<Compile Include="TypeSystem\ExtensionInfo.cs" />
<Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" /> <Compile Include="TypeSystem\ITypeDefinitionOrUnknown.cs" />
<Compile Include="Util\BitOperations.cs" /> <Compile Include="Util\BitOperations.cs" />
<Compile Include="Util\DelegateComparer.cs" /> <Compile Include="Util\DelegateComparer.cs" />

176
ICSharpCode.Decompiler/TypeSystem/ExtensionInfo.cs

@ -0,0 +1,176 @@
// Copyright (c) 2025 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
namespace ICSharpCode.Decompiler.TypeSystem
{
public class ExtensionInfo
{
readonly Dictionary<IMember, ExtensionMemberInfo> extensionMemberMap;
readonly Dictionary<IMember, ExtensionMemberInfo> implementationMemberMap;
public ExtensionInfo(MetadataModule module, ITypeDefinition extensionContainer)
{
this.extensionMemberMap = new();
this.implementationMemberMap = new();
var metadata = module.MetadataFile.Metadata;
foreach (var extGroup in extensionContainer.NestedTypes)
{
if (!(extGroup is { Kind: TypeKind.Class, IsSealed: true }
&& extGroup.Name.StartsWith("<>E__", System.StringComparison.Ordinal)))
{
continue;
}
TypeDefinition td = metadata.GetTypeDefinition((TypeDefinitionHandle)extGroup.MetadataToken);
IMethod? marker = null;
bool hasMultipleMarkers = false;
List<IMethod> extensionMethods = [];
// For easier access to accessors we use SRM
foreach (var h in td.GetMethods())
{
var method = module.GetDefinition(h);
if (method.SymbolKind is SymbolKind.Constructor)
continue;
if (method is { Name: "<Extension>$", IsStatic: true, Parameters.Count: 1 })
{
if (marker == null)
marker = method;
else
hasMultipleMarkers = true;
continue;
}
extensionMethods.Add(method);
}
if (marker == null || hasMultipleMarkers)
continue;
foreach (var extension in extensionMethods)
{
int expectedTypeParameterCount = extension.TypeParameters.Count + extGroup.TypeParameterCount;
bool hasInstance = !extension.IsStatic;
int parameterOffset = hasInstance ? 1 : 0;
int expectedParameterCount = extension.Parameters.Count + parameterOffset;
TypeParameterSubstitution subst = new TypeParameterSubstitution([], [.. extGroup.TypeArguments, .. extension.TypeArguments]);
bool IsMatchingImplementation(IMethod impl)
{
if (!impl.IsStatic)
return false;
if (extension.Name != impl.Name)
return false;
if (expectedTypeParameterCount != impl.TypeParameters.Count)
return false;
if (expectedParameterCount != impl.Parameters.Count)
return false;
if (hasInstance)
{
IType ti = impl.Parameters[0].Type.AcceptVisitor(subst);
IType tm = marker.Parameters.Single().Type;
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(ti, tm))
return false;
}
for (int i = 0; i < extension.Parameters.Count; i++)
{
IType ti = impl.Parameters[i + parameterOffset].Type.AcceptVisitor(subst);
IType tm = extension.Parameters[i].Type;
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(ti, tm))
return false;
}
return NormalizeTypeVisitor.TypeErasure.EquivalentTypes(
impl.ReturnType.AcceptVisitor(subst),
extension.ReturnType
);
}
foreach (var impl in extensionContainer.Methods)
{
if (!IsMatchingImplementation(impl))
continue;
var emi = new ExtensionMemberInfo(marker, extension, impl);
extensionMemberMap[extension] = emi;
implementationMemberMap[impl] = emi;
}
}
}
}
public ExtensionMemberInfo? InfoOfExtensionMember(IMethod method)
{
return this.extensionMemberMap.TryGetValue(method, out var value) ? value : null;
}
public ExtensionMemberInfo? InfoOfImplementationMember(IMethod method)
{
return this.implementationMemberMap.TryGetValue(method, out var value) ? value : null;
}
public IEnumerable<IGrouping<IMethod, ExtensionMemberInfo>> GetGroups()
{
return this.extensionMemberMap.Values.GroupBy(x => x.ExtensionMarkerMethod);
}
}
public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation)
{
/// <summary>
/// Metadata-only method called '&lt;Extension&gt;$'. Has the C# signature for the extension declaration.
///
/// <code>extension(ReceiverType name) {} -> void &lt;Extension&gt;$(ReceiverType name) {}</code>
/// </summary>
public readonly IMethod ExtensionMarkerMethod = marker;
/// <summary>
/// Metadata-only method with a signature as declared in C# within the extension declaration.
/// This could also be an accessor of an extension property.
/// </summary>
public readonly IMethod ExtensionMember = extension;
/// <summary>
/// The actual implementation method in the outer class. The signature is a concatenation
/// of the extension marker and the extension member's signatures.
/// </summary>
public readonly IMethod ImplementationMethod = implementation;
/// <summary>
/// This is the enclosing static class.
/// </summary>
public ITypeDefinition ExtensionContainer => ImplementationMethod.DeclaringTypeDefinition!;
/// <summary>
/// This is the compiler-generated class containing the extension members. Has type parameters
/// from the extension declaration with minimal constraints.
/// </summary>
public ITypeDefinition ExtensionGroupingType => ExtensionMember.DeclaringTypeDefinition!;
/// <summary>
/// This class holds the type parameters for the extension declaration with full fidelity of C# constraints.
/// </summary>
public ITypeDefinition ExtensionMarkerType => ExtensionMarkerMethod.DeclaringTypeDefinition!;
}
}

7
ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs

@ -28,6 +28,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary> /// </summary>
public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity public interface ITypeDefinition : ITypeDefinitionOrUnknown, IType, IEntity
{ {
ExtensionInfo? ExtensionInfo { get; }
IReadOnlyList<ITypeDefinition> NestedTypes { get; } IReadOnlyList<ITypeDefinition> NestedTypes { get; }
IReadOnlyList<IMember> Members { get; } IReadOnlyList<IMember> Members { get; }
@ -71,10 +72,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
new IType? DeclaringType { get; } // solves ambiguity between IType.DeclaringType and IEntity.DeclaringType new IType? DeclaringType { get; } // solves ambiguity between IType.DeclaringType and IEntity.DeclaringType
/// <summary> /// <summary>
/// Gets whether this type contains extension methods. /// Gets whether this type contains extension methods or C# 14 extensions.
/// </summary> /// </summary>
/// <remarks>This property is used to speed up the search for extension methods.</remarks> /// <remarks>This property is used to speed up the search for extension members.</remarks>
bool HasExtensionMethods { get; } bool HasExtensions { get; }
/// <summary> /// <summary>
/// The nullability specified in the [NullableContext] attribute on the type. /// The nullability specified in the [NullableContext] attribute on the type.

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

@ -41,6 +41,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
// eagerly loaded: // eagerly loaded:
readonly FullTypeName fullTypeName; readonly FullTypeName fullTypeName;
readonly TypeAttributes attributes; readonly TypeAttributes attributes;
public TypeKind Kind { get; } public TypeKind Kind { get; }
public bool IsByRefLike { get; } public bool IsByRefLike { get; }
public bool IsReadOnly { get; } public bool IsReadOnly { get; }
@ -48,7 +49,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
public IReadOnlyList<ITypeParameter> TypeParameters { get; } public IReadOnlyList<ITypeParameter> TypeParameters { get; }
public KnownTypeCode KnownTypeCode { get; } public KnownTypeCode KnownTypeCode { get; }
public IType EnumUnderlyingType { get; } public IType EnumUnderlyingType { get; }
public bool HasExtensionMethods { get; } public bool HasExtensions { get; }
public Nullability NullableContext { get; } public Nullability NullableContext { get; }
// lazy-loaded: // lazy-loaded:
@ -132,7 +133,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
else else
{ {
this.Kind = TypeKind.Class; this.Kind = TypeKind.Class;
this.HasExtensionMethods = this.IsStatic this.HasExtensions = this.IsStatic
&& (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods && (module.TypeSystemOptions & TypeSystemOptions.ExtensionMethods) == TypeSystemOptions.ExtensionMethods
&& td.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension); && td.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.Extension);
} }
@ -143,6 +144,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
return $"{MetadataTokens.GetToken(handle):X8} {fullTypeName}"; return $"{MetadataTokens.GetToken(handle):X8} {fullTypeName}";
} }
private ExtensionInfo extensionInfo;
public ExtensionInfo ExtensionInfo {
get {
if (!HasExtensions)
return null;
var extensionInfo = LazyInit.VolatileRead(ref this.extensionInfo);
if (extensionInfo != null)
return extensionInfo;
extensionInfo = new ExtensionInfo(module, this);
if ((module.TypeSystemOptions & TypeSystemOptions.Uncached) != 0)
return extensionInfo;
return LazyInit.GetOrSet(ref this.extensionInfo, extensionInfo);
}
}
ITypeDefinition[] nestedTypes; ITypeDefinition[] nestedTypes;
public IReadOnlyList<ITypeDefinition> NestedTypes { public IReadOnlyList<ITypeDefinition> NestedTypes {

3
ICSharpCode.Decompiler/TypeSystem/Implementation/MinimalCorlib.cs

@ -173,7 +173,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IType IType.DeclaringType => null; IType IType.DeclaringType => null;
IType IEntity.DeclaringType => null; IType IEntity.DeclaringType => null;
bool ITypeDefinition.HasExtensionMethods => false; bool ITypeDefinition.HasExtensions => false;
ExtensionInfo ITypeDefinition.ExtensionInfo => null;
bool ITypeDefinition.IsReadOnly => false; bool ITypeDefinition.IsReadOnly => false;
TypeKind IType.Kind => typeKind; TypeKind IType.Kind => typeKind;

2
ICSharpCode.ILSpyX/Analyzers/Builtin/TypeExtensionMethodsAnalyzer.cs

@ -46,7 +46,7 @@ namespace ICSharpCode.ILSpyX.Analyzers.Builtin
IEnumerable<IEntity> ScanType(ITypeDefinition analyzedType, ITypeDefinition type, AnalyzerContext context) IEnumerable<IEntity> ScanType(ITypeDefinition analyzedType, ITypeDefinition type, AnalyzerContext context)
{ {
if (!type.HasExtensionMethods) if (!type.HasExtensions)
yield break; yield break;
if (analyzedType.ParentModule?.MetadataFile == null) if (analyzedType.ParentModule?.MetadataFile == null)

1
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -242,6 +242,7 @@ namespace ICSharpCode.ILSpy
case "class": case "class":
case "interface": case "interface":
case "delegate": case "delegate":
case "extension":
color = referenceTypeKeywordsColor; color = referenceTypeKeywordsColor;
break; break;
case "record": case "record":

1
ILSpy/Languages/CSharpLanguage.cs

@ -117,6 +117,7 @@ namespace ICSharpCode.ILSpy
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp11_0.ToString(), "C# 11.0 / VS 2022.4"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp13_0.ToString(), "C# 13.0 / VS 2022.12"), new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp13_0.ToString(), "C# 13.0 / VS 2022.12"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp14_0.ToString(), "C# 14.0 / VS 202x.yy"),
}; };
} }
return versions; return versions;

Loading…
Cancel
Save