Skip to content

Commit 00baf1c

Browse files
WIP ExtensionInfo
1 parent b669bec commit 00baf1c

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright (c) 2025 Daniel Grunwald
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
#nullable enable
20+
21+
using System.Collections.Generic;
22+
using System.Linq;
23+
using System.Reflection.Metadata;
24+
25+
namespace ICSharpCode.Decompiler.TypeSystem
26+
{
27+
public class ExtensionInfo
28+
{
29+
readonly Dictionary<IMember, ExtensionMemberInfo> extensionMemberMap;
30+
readonly Dictionary<IMember, ExtensionMemberInfo> implementationMemberMap;
31+
32+
public ExtensionInfo(MetadataModule module, ITypeDefinition extensionContainer)
33+
{
34+
this.extensionMemberMap = new();
35+
this.implementationMemberMap = new();
36+
37+
var metadata = module.MetadataFile.Metadata;
38+
39+
foreach (var extGroup in extensionContainer.NestedTypes)
40+
{
41+
if (!(extGroup is { Kind: TypeKind.Class, IsSealed: true }
42+
&& extGroup.HasAttribute(KnownAttribute.SpecialName)
43+
&& extGroup.Name.StartsWith("<>E__", System.StringComparison.Ordinal)))
44+
{
45+
continue;
46+
}
47+
48+
TypeDefinition td = metadata.GetTypeDefinition((TypeDefinitionHandle)extGroup.MetadataToken);
49+
IMethod? marker = null;
50+
bool hasMultipleMarkers = false;
51+
List<IMethod> extensionMethods = [];
52+
53+
// For easier access to accessors we use SRM
54+
foreach (var h in td.GetMethods())
55+
{
56+
var method = module.GetDefinition(h);
57+
58+
if (method.SymbolKind is SymbolKind.Constructor)
59+
continue;
60+
if (method is { Name: "<Extension>$", IsStatic: true, Parameters.Count: 1 })
61+
{
62+
if (marker == null)
63+
marker = method;
64+
else
65+
hasMultipleMarkers = true;
66+
continue;
67+
}
68+
69+
extensionMethods.Add(method);
70+
}
71+
72+
if (marker == null || hasMultipleMarkers)
73+
continue;
74+
75+
foreach (var extension in extensionMethods)
76+
{
77+
int expectedTypeParameterCount = extension.TypeParameters.Count + extGroup.TypeParameterCount;
78+
bool hasInstance = !extension.IsStatic;
79+
int parameterOffset = hasInstance ? 1 : 0;
80+
int expectedParameterCount = extension.Parameters.Count + parameterOffset;
81+
TypeParameterSubstitution subst = new TypeParameterSubstitution([], [.. extGroup.TypeArguments, .. extension.TypeArguments]);
82+
83+
bool IsMatchingImplementation(IMethod impl)
84+
{
85+
if (!impl.IsStatic)
86+
return false;
87+
if (extension.Name != impl.Name)
88+
return false;
89+
if (expectedTypeParameterCount != impl.TypeParameters.Count)
90+
return false;
91+
if (expectedParameterCount != impl.Parameters.Count)
92+
return false;
93+
if (hasInstance)
94+
{
95+
IType ti = impl.Parameters[0].Type.AcceptVisitor(subst);
96+
IType tm = marker.Parameters.Single().Type;
97+
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(ti, tm))
98+
return false;
99+
}
100+
for (int i = 0; i < extension.Parameters.Count; i++)
101+
{
102+
IType ti = impl.Parameters[i + parameterOffset].Type.AcceptVisitor(subst);
103+
IType tm = extension.Parameters[i].Type;
104+
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(ti, tm))
105+
return false;
106+
}
107+
return NormalizeTypeVisitor.TypeErasure.EquivalentTypes(
108+
impl.ReturnType.AcceptVisitor(subst),
109+
extension.ReturnType
110+
);
111+
}
112+
113+
foreach (var impl in extensionContainer.Methods)
114+
{
115+
if (!IsMatchingImplementation(impl))
116+
continue;
117+
var emi = new ExtensionMemberInfo(marker, extension, impl);
118+
extensionMemberMap[extension] = emi;
119+
implementationMemberMap[impl] = emi;
120+
}
121+
}
122+
}
123+
124+
}
125+
126+
public ExtensionMemberInfo? InfoOfExtensionMember(IMethod method)
127+
{
128+
return this.extensionMemberMap.TryGetValue(method, out var value) ? value : null;
129+
}
130+
131+
public ExtensionMemberInfo? InfoOfImplementationMember(IMethod method)
132+
{
133+
return this.implementationMemberMap.TryGetValue(method, out var value) ? value : null;
134+
}
135+
}
136+
137+
public readonly struct ExtensionMemberInfo(IMethod marker, IMethod extension, IMethod implementation)
138+
{
139+
/// <summary>
140+
/// Metadata-only method called '&lt;Extension&gt;$'. Has the C# signature for the extension declaration.
141+
///
142+
/// <code>extension(ReceiverType name) {} -> void &lt;Extension&gt;$(ReceiverType name) {}</code>
143+
/// </summary>
144+
public readonly IMethod ExtensionMarkerMethod = marker;
145+
/// <summary>
146+
/// Metadata-only method with a signature as declared in C# within the extension declaration.
147+
/// This could also be an accessor of an extension property.
148+
/// </summary>
149+
public readonly IMethod ExtensionMember = extension;
150+
/// <summary>
151+
/// The actual implementation method in the outer class. The signature is a concatenation
152+
/// of the extension marker and the extension member's signatures.
153+
/// </summary>
154+
public readonly IMethod ImplementationMethod = implementation;
155+
156+
/// <summary>
157+
/// This is the enclosing static class.
158+
/// </summary>
159+
public ITypeDefinition ExtensionContainer => ImplementationMethod.DeclaringTypeDefinition!;
160+
161+
/// <summary>
162+
/// This is the compiler-generated class containing the extension members. Has type parameters
163+
/// from the extension declaration with minimal constraints.
164+
/// </summary>
165+
public ITypeDefinition ExtensionGroupingType => ExtensionMember.DeclaringTypeDefinition!;
166+
167+
/// <summary>
168+
/// This class holds the type parameters for the extension declaration with full fidelity of C# constraints.
169+
/// </summary>
170+
public ITypeDefinition ExtensionMarkerType => ExtensionMarkerMethod.DeclaringTypeDefinition!;
171+
}
172+
}

0 commit comments

Comments
 (0)