mirror of https://github.com/icsharpcode/ILSpy.git
23 changed files with 520 additions and 34 deletions
@ -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() |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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 '<Extension>$'. Has the C# signature for the extension declaration.
|
||||||
|
///
|
||||||
|
/// <code>extension(ReceiverType name) {} -> void <Extension>$(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!; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue