Skip to content

Commit cdc5fd7

Browse files
author
Julien Couvreur
authored
Extensions: AssociatedExtensionImplementation on constructed symbols (#80423)
1 parent 4fec8b9 commit cdc5fd7

File tree

4 files changed

+174
-8
lines changed

4 files changed

+174
-8
lines changed

src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Reflection.Metadata;
1010
using System.Threading;
1111
using Microsoft.Cci;
12+
using Microsoft.CodeAnalysis.PooledObjects;
1213
using Roslyn.Utilities;
1314

1415
namespace Microsoft.CodeAnalysis.CSharp.Symbols.PublicModel
@@ -337,12 +338,28 @@ INamedTypeSymbol IMethodSymbol.AssociatedAnonymousDelegate
337338
{
338339
get
339340
{
340-
if (!_underlying.IsDefinition || !_underlying.GetIsNewExtensionMember())
341+
if (!_underlying.GetIsNewExtensionMember())
341342
{
342343
return null;
343344
}
344345

345-
return _underlying.TryGetCorrespondingExtensionImplementationMethod().GetPublicSymbol();
346+
var implDefinition = _underlying.OriginalDefinition.TryGetCorrespondingExtensionImplementationMethod();
347+
if (implDefinition is null)
348+
{
349+
return null;
350+
}
351+
352+
var enclosing = _underlying.ContainingType.ContainingType;
353+
var implementation = implDefinition.AsMember(enclosing);
354+
if (implementation.Arity != 0)
355+
{
356+
var typeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance(implementation.Arity);
357+
typeArguments.AddRange(_underlying.ContainingType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics);
358+
typeArguments.AddRange(_underlying.TypeArgumentsWithAnnotations);
359+
implementation = implementation.Construct(typeArguments.ToImmutableAndFree());
360+
}
361+
362+
return implementation.GetPublicSymbol();
346363
}
347364
}
348365
#nullable disable

src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3867,6 +3867,9 @@ .method public hidebysig static void M<class T> () cil managed
38673867
// (1,5): error CS0570: 'E.extension(int).M<T>()' is not supported by the language
38683868
// int.M();
38693869
Diagnostic(ErrorCode.ERR_BindToBogus, "M").WithArguments("E.extension(int).M<T>()").WithLocation(1, 5));
3870+
3871+
var method = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single().GetMember<MethodSymbol>("M").GetPublicSymbol();
3872+
Assert.Null(method.AssociatedExtensionImplementation);
38703873
}
38713874

38723875
[Fact]
@@ -33081,8 +33084,7 @@ public void M() { }
3308133084
var model = comp.GetSemanticModel(tree);
3308233085
var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "42.M");
3308333086
var method = (IMethodSymbol)model.GetSymbolInfo(memberAccess).Symbol;
33084-
// Tracked by https://github.com/dotnet/roslyn/issues/78957 : public API, add support for constructed symbols
33085-
Assert.Null(method.AssociatedExtensionImplementation);
33087+
AssertEx.Equal("void E.M<System.Int32>(this System.Int32 t)", method.AssociatedExtensionImplementation.ToTestDisplayString());
3308633088
Assert.Equal("void E.M<T>(this T t)", method.OriginalDefinition.AssociatedExtensionImplementation.ToTestDisplayString());
3308733089
}
3308833090

@@ -33188,6 +33190,151 @@ .method family hidebysig static void M () cil managed
3318833190
Assert.Null(method.AssociatedExtensionImplementation);
3318933191
}
3319033192

33193+
[Fact]
33194+
public void AssociatedExtensionImplementation_06()
33195+
{
33196+
// not a definition, generic extension and method
33197+
var src = """
33198+
42.M(43L, "");
33199+
33200+
public static class E
33201+
{
33202+
extension<T>(T t)
33203+
{
33204+
public void M<U, V>(U u, V v) { }
33205+
}
33206+
}
33207+
""";
33208+
var comp = CreateCompilation(src);
33209+
comp.VerifyEmitDiagnostics();
33210+
33211+
var tree = comp.SyntaxTrees.First();
33212+
var model = comp.GetSemanticModel(tree);
33213+
var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "42.M");
33214+
var method = (IMethodSymbol)model.GetSymbolInfo(memberAccess).Symbol;
33215+
AssertEx.Equal("void E.M<System.Int32, System.Int64, System.String>(this System.Int32 t, System.Int64 u, System.String v)",
33216+
method.AssociatedExtensionImplementation.ToTestDisplayString());
33217+
}
33218+
33219+
[Fact]
33220+
public void AssociatedExtensionImplementation_07()
33221+
{
33222+
// generic definition
33223+
var src = """
33224+
public static class E
33225+
{
33226+
extension<T>(T t)
33227+
{
33228+
public void M<U, V>(U u, V v)
33229+
{
33230+
t.M(u, v);
33231+
}
33232+
}
33233+
}
33234+
""";
33235+
var comp = CreateCompilation(src);
33236+
comp.VerifyEmitDiagnostics();
33237+
33238+
var tree = comp.SyntaxTrees.First();
33239+
var model = comp.GetSemanticModel(tree);
33240+
var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "t.M");
33241+
var method = (IMethodSymbol)model.GetSymbolInfo(memberAccess).Symbol;
33242+
Assert.True(method.IsDefinition);
33243+
var associated = method.AssociatedExtensionImplementation;
33244+
AssertEx.Equal("void E.M<T, U, V>(this T t, U u, V v)", associated.ToTestDisplayString());
33245+
Assert.False(associated.IsDefinition);
33246+
}
33247+
33248+
[Fact]
33249+
public void AssociatedExtensionImplementation_08()
33250+
{
33251+
// not a definition, generic extension and method, partially constructed with type parameters
33252+
var src = """
33253+
public static class E
33254+
{
33255+
extension<T>(T t)
33256+
{
33257+
public void M<U, V>(U u, V v)
33258+
{
33259+
t.M(42L, "");
33260+
}
33261+
}
33262+
}
33263+
""";
33264+
var comp = CreateCompilation(src);
33265+
comp.VerifyEmitDiagnostics();
33266+
33267+
var tree = comp.SyntaxTrees.First();
33268+
var model = comp.GetSemanticModel(tree);
33269+
var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "t.M");
33270+
var method = (IMethodSymbol)model.GetSymbolInfo(memberAccess).Symbol;
33271+
AssertEx.Equal("void E.M<T, System.Int64, System.String>(this T t, System.Int64 u, System.String v)",
33272+
method.AssociatedExtensionImplementation.ToTestDisplayString());
33273+
}
33274+
33275+
[Fact]
33276+
public void AssociatedExtensionImplementation_09()
33277+
{
33278+
// generic enclosing class, generic implementation method
33279+
var src = """
33280+
public static class E<T0>
33281+
{
33282+
extension<T1>(T1 t1)
33283+
{
33284+
public void M<T2>(T2 t2)
33285+
{
33286+
}
33287+
}
33288+
}
33289+
""";
33290+
var comp = CreateCompilation(src);
33291+
comp.VerifyEmitDiagnostics(
33292+
// (3,5): error CS9283: Extensions must be declared in a top-level, non-generic, static class
33293+
// extension<T1>(T1 t1)
33294+
Diagnostic(ErrorCode.ERR_BadExtensionContainingType, "extension").WithLocation(3, 5));
33295+
33296+
var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers("").Single();
33297+
var method = extension.GetMethod("M").GetPublicSymbol();
33298+
Assert.True(method.IsDefinition);
33299+
var associated = method.AssociatedExtensionImplementation;
33300+
AssertEx.Equal("void E<T0>.M<T1, T2>(this T1 t1, T2 t2)", associated.ToTestDisplayString());
33301+
Assert.False(associated.IsDefinition);
33302+
33303+
var e = comp.GlobalNamespace.GetTypeMember("E");
33304+
var constructedE = e.Construct(comp.GetSpecialType(SpecialType.System_Int32));
33305+
var constructedMethod = constructedE.GetTypeMembers("").Single().GetMethod("M").GetPublicSymbol();
33306+
AssertEx.Equal("void E<System.Int32>.<G>$8048A6C8BE30A622530249B904B537EB<T1>.M<T2>(T2 t2)", constructedMethod.ToTestDisplayString());
33307+
AssertEx.Equal("void E<System.Int32>.M<T1, T2>(this T1 t1, T2 t2)", constructedMethod.AssociatedExtensionImplementation.ToTestDisplayString());
33308+
}
33309+
33310+
[Fact]
33311+
public void AssociatedExtensionImplementation_10()
33312+
{
33313+
// generic enclosing class, non-generic implementation method
33314+
var src = """
33315+
public static class E<T0>
33316+
{
33317+
extension(int i)
33318+
{
33319+
public void M()
33320+
{
33321+
}
33322+
}
33323+
}
33324+
""";
33325+
var comp = CreateCompilation(src);
33326+
comp.VerifyEmitDiagnostics(
33327+
// (3,5): error CS9283: Extensions must be declared in a top-level, non-generic, static class
33328+
// extension<T1>(T1 t1)
33329+
Diagnostic(ErrorCode.ERR_BadExtensionContainingType, "extension").WithLocation(3, 5));
33330+
33331+
var e = comp.GlobalNamespace.GetTypeMember("E");
33332+
var constructedE = e.Construct(comp.GetSpecialType(SpecialType.System_Int32));
33333+
var constructedMethod = constructedE.GetTypeMembers("").Single().GetMethod("M").GetPublicSymbol();
33334+
AssertEx.Equal("void E<System.Int32>.<G>$BA41CFE2B5EDAEB8C1B9062F59ED4D69.M()", constructedMethod.ToTestDisplayString());
33335+
AssertEx.Equal("void E<System.Int32>.M(this System.Int32 i)", constructedMethod.AssociatedExtensionImplementation.ToTestDisplayString());
33336+
}
33337+
3319133338
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/78606")]
3319233339
public void DocumentationCommentId_01()
3319333340
{

src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ public interface IMethodSymbol : ISymbol
305305
/// </summary>
306306
bool IsIterator { get; }
307307

308-
// Tracked by https://github.com/dotnet/roslyn/issues/78957 : public API, add support for constructed symbols
309308
/// <summary>
310309
/// For a method/accessor/operator in an extension block, returns the corresponding implementation method if one exists.
311310
/// Returns null otherwise.
@@ -321,7 +320,10 @@ public interface IMethodSymbol : ISymbol
321320
/// }
322321
/// </code>
323322
/// When given the method symbol for <c>E.extension(int i).M()</c>,
324-
/// it will return the corresponding static implementation method <c>E.M(this int i)</c>.
323+
/// it returns the corresponding static implementation method <c>E.M(this int i)</c>.
324+
///
325+
/// When given a generic extension member definition, it returns an implementation method constructed
326+
/// with the extension member's type parameters.
325327
/// </summary>
326328
IMethodSymbol? AssociatedExtensionImplementation { get; }
327329
}

src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -830,9 +830,9 @@ static IEnumerable<ISymbol> GetMembersExceptExtensionImplementations(INamedTypeS
830830
{
831831
foreach (var extensionMember in nested.GetMembers())
832832
{
833-
if (extensionMember is IMethodSymbol { OriginalDefinition.AssociatedExtensionImplementation: { } toShadow })
833+
if (extensionMember is IMethodSymbol { AssociatedExtensionImplementation: { } toShadow })
834834
{
835-
implementationsToHide.Add(toShadow);
835+
implementationsToHide.Add(toShadow.OriginalDefinition);
836836
}
837837
}
838838
}

0 commit comments

Comments
 (0)