From a15101844974a2737d07a1fdc15ec9cb0d42782f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Sat, 11 Oct 2025 09:24:38 -0700 Subject: [PATCH 1/4] Extensions: Add ReduceExtensionMember API --- .../Portable/Binder/Binder_Expressions.cs | 4 +- .../CSharp/Portable/Binder/Binder_Symbols.cs | 2 +- .../OverloadResolution/MethodTypeInference.cs | 15 +- .../Compilation/CSharpSemanticModel.cs | 2 +- .../Portable/Symbols/ConstraintsHelper.cs | 2 - .../Symbols/MemberSymbolExtensions.cs | 13 +- .../CSharp/Portable/Symbols/MethodSymbol.cs | 6 +- .../Symbols/PublicModel/MethodSymbol.cs | 15 +- .../Symbols/PublicModel/PropertySymbol.cs | 11 + .../Source/SourceNamedTypeSymbol_Extension.cs | 37 +- .../Test/Emit3/Semantics/ExtensionTests.cs | 134 +++++ .../Test/Emit3/Semantics/ExtensionTests2.cs | 462 ++++++++++++++++++ .../Core/Portable/PublicAPI.Unshipped.txt | 2 + .../Core/Portable/Symbols/IMethodSymbol.cs | 6 + .../Core/Portable/Symbols/IPropertySymbol.cs | 6 + .../Portable/Symbols/MethodSymbol.vb | 4 + .../Portable/Symbols/PropertySymbol.vb | 3 + .../ExtensionMethods/ExtensionMethodTests.vb | 24 + ...dataAsSourceService.WrappedMethodSymbol.cs | 8 +- ...taAsSourceService.WrappedPropertySymbol.cs | 3 + .../Apis/Microsoft.CodeAnalysis.txt | 2 + .../CodeGenerationAbstractMethodSymbol.cs | 3 + .../Symbols/CodeGenerationPropertySymbol.cs | 3 + 23 files changed, 739 insertions(+), 28 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 2756ad1f2f14a..6405ea6ba3e09 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -8768,12 +8768,12 @@ static MethodGroupResolution resolveMethods( bool inapplicable = false; if (method.IsExtensionMethod - && (object)method.ReduceExtensionMethod(receiverType, binder.Compilation) == null) + && method.ReduceExtensionMethod(receiverType, binder.Compilation) is null) { inapplicable = true; } else if (method.GetIsNewExtensionMember() - && SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, method, receiverType) == null) + && SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, method, receiverType, wasExtensionFullyInferred: out _) is null) { inapplicable = true; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index c6b8231e3c30c..3dd663baa50eb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -1585,7 +1585,7 @@ private void CheckWhatCandidatesWeHave( else { Debug.Assert(symbol.GetIsNewExtensionMember()); - if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(this.Compilation, symbol, receiverType) is { } compatibleSubstitutedMember) + if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(this.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) { if (compatibleSubstitutedMember.IsStatic) { diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index a9e6ef6558b91..c9e06b1059a97 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs @@ -129,7 +129,9 @@ private enum Dependency Indirect = 0x12 } - private readonly CSharpCompilation _compilation; +#nullable enable + private readonly CSharpCompilation? _compilation; +#nullable disable private readonly ConversionsBase _conversions; private readonly ImmutableArray _methodTypeParameters; private readonly NamedTypeSymbol _constructedContainingTypeOfMethod; @@ -320,14 +322,14 @@ public static MethodTypeInferenceResult Infer( #nullable enable private MethodTypeInferrer( - CSharpCompilation compilation, + CSharpCompilation? compilation, ConversionsBase conversions, ImmutableArray methodTypeParameters, NamedTypeSymbol constructedContainingTypeOfMethod, ImmutableArray formalParameterTypes, ImmutableArray formalParameterRefKinds, ImmutableArray arguments, - Extensions extensions, + Extensions? extensions, Dictionary? ordinals) { _compilation = compilation; @@ -2856,7 +2858,7 @@ private bool Fix(int iParam, ref CompoundUseSiteInfo useSiteInfo } private static (TypeWithAnnotations Type, bool FromFunctionType) Fix( - CSharpCompilation compilation, + CSharpCompilation? compilation, ConversionsBase conversions, TypeParameterSymbol typeParameter, HashSet? exact, @@ -2989,6 +2991,7 @@ private static (TypeWithAnnotations Type, bool FromFunctionType) Fix( var resultType = functionType.GetInternalDelegateType(); if (hasExpressionTypeConstraint(typeParameter)) { + Debug.Assert(compilation is not null); // Tracked by https://github.com/dotnet/roslyn/issues/80658 var expressionOfTType = compilation.GetWellKnownType(WellKnownType.System_Linq_Expressions_Expression_T); resultType = expressionOfTType.Construct(resultType); } @@ -3179,6 +3182,7 @@ private static NamedTypeSymbol GetInterfaceInferenceBound(ImmutableArray /// We apply type inference to an extension type, using the receiver as argument against the /// extension parameter. @@ -3187,7 +3191,7 @@ private static NamedTypeSymbol GetInterfaceInferenceBound(ImmutableArray InferTypeArgumentsFromReceiverType( NamedTypeSymbol extension, BoundExpression receiver, - CSharpCompilation compilation, + CSharpCompilation? compilation, ConversionsBase conversions, ref CompoundUseSiteInfo useSiteInfo) { @@ -3216,6 +3220,7 @@ public static ImmutableArray InferTypeArgumentsFromReceiver return inferrer.GetInferredTypeArguments(out _); } +#nullable disable //////////////////////////////////////////////////////////////////////////////// // diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index d517f3b707cd6..854bf9d594483 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -1681,7 +1681,7 @@ private ImmutableArray LookupSymbolsInternal( else { Debug.Assert(symbol.GetIsNewExtensionMember()); - if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, symbol, receiverType) is { } compatibleSubstitutedMember) + if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) { results.Add(compatibleSubstitutedMember.GetPublicSymbol()); } diff --git a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs index 34a53d2b4702a..6d95a142360a7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs @@ -717,8 +717,6 @@ public static bool CheckConstraintsForNamedType( public static bool CheckConstraints(this NamedTypeSymbol type, in CheckConstraintsArgs args) { - Debug.Assert(args.CurrentCompilation is object); - if (!RequiresChecking(type)) { return true; diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 39bc22054c6cc..17ca582bdb6ba 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -281,9 +281,8 @@ internal static TMember ConstructIncludingExtension(this TMember member else { Debug.Assert(method.GetIsNewExtensionMember()); - constructed = (MethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType); - - if (checkFullyInferred && constructed?.IsGenericMethod == true && typeArguments.IsDefaultOrEmpty) + constructed = (MethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType, out bool wasExtensionFullyInferred); + if (checkFullyInferred && (!wasExtensionFullyInferred || (constructed?.IsGenericMethod == true && typeArguments.IsDefaultOrEmpty))) { return null; } @@ -297,7 +296,13 @@ internal static TMember ConstructIncludingExtension(this TMember member // infer type arguments based off the receiver type if needed, check applicability Debug.Assert(receiverType is not null); Debug.Assert(property.GetIsNewExtensionMember()); - return (PropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType); + var result = (PropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType, wasExtensionFullyInferred: out bool wasFullyInferred); + if (checkFullyInferred && !wasFullyInferred) + { + return null; + } + + return result; } throw ExceptionUtilities.UnexpectedValue(member.Kind); diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 36afe4b30c710..5825133eab45c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -743,7 +743,8 @@ public override TResult Accept(CSharpSymbolVisitor visitor) return visitor.VisitMethod(this); } - public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation) +#nullable enable + public MethodSymbol? ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation? compilation) { return ReduceExtensionMethod(receiverType, compilation, wasFullyInferred: out _); } @@ -755,7 +756,7 @@ public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompila /// The compilation in which constraints should be checked. /// Should not be null, but if it is null we treat constraints as we would in the latest /// language version. - public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation, out bool wasFullyInferred) + public MethodSymbol? ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation? compilation, out bool wasFullyInferred) { if ((object)receiverType == null) { @@ -770,6 +771,7 @@ public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompila return ReducedExtensionMethodSymbol.Create(this, receiverType, compilation, out wasFullyInferred); } +#nullable disable /// /// If this is an extension method, returns a reduced extension method diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs index 9a523b6c32f97..10d567b679957 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs @@ -201,13 +201,26 @@ ITypeSymbol IMethodSymbol.GetTypeInferredDuringReduction(ITypeParameterSymbol re GetPublicSymbol(); } - IMethodSymbol IMethodSymbol.ReduceExtensionMethod(ITypeSymbol receiverType) +#nullable enable + IMethodSymbol? IMethodSymbol.ReduceExtensionMethod(ITypeSymbol receiverType) { return _underlying.ReduceExtensionMethod( receiverType.EnsureCSharpSymbolOrNull(nameof(receiverType)), compilation: null). GetPublicSymbol(); } + IMethodSymbol? IMethodSymbol.ReduceExtensionMember(ITypeSymbol receiverType) + { + if (_underlying.GetIsNewExtensionMember() && SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(_underlying)) + { + var csharpReceiver = receiverType.EnsureCSharpSymbolOrNull(nameof(receiverType)); + return (IMethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); + } + + return null; + } +#nullable disable + ImmutableArray IMethodSymbol.ExplicitInterfaceImplementations { get diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs index 4179ba6c272b9..7047fdaf03d7c 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs @@ -115,6 +115,17 @@ ImmutableArray IPropertySymbol.RefCustomModifiers IPropertySymbol? IPropertySymbol.PartialImplementationPart => _underlying.PartialImplementationPart.GetPublicSymbol(); bool IPropertySymbol.IsPartialDefinition => (_underlying as SourcePropertySymbol)?.IsPartialDefinition ?? false; + + IPropertySymbol? IPropertySymbol.ReduceExtensionMember(ITypeSymbol receiverType) + { + if (_underlying.GetIsNewExtensionMember() && SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(_underlying)) + { + var csharpReceiver = receiverType.EnsureCSharpSymbolOrNull(nameof(receiverType)); + return (IPropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); + } + + return null; + } #nullable disable #region ISymbol Members diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs index b48b1ad799976..4e2c24af2ebe7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs @@ -1168,20 +1168,25 @@ private static string RawNameToHashString(string rawName) return CodeAnalysis.CodeGen.PrivateImplementationDetails.HashToHex(hash); } - internal static Symbol? GetCompatibleSubstitutedMember(CSharpCompilation compilation, Symbol extensionMember, TypeSymbol receiverType) + /// + /// Given a receiver type, check if we can infer type arguments for the extension block and check for compatibility. + /// If that is successful, return the substituted extension member and whether the extension block was fully inferred. + /// + internal static Symbol? GetCompatibleSubstitutedMember(CSharpCompilation? compilation, Symbol extensionMember, TypeSymbol receiverType, out bool wasExtensionFullyInferred) { Debug.Assert(extensionMember.GetIsNewExtensionMember()); NamedTypeSymbol extension = extensionMember.ContainingType; if (extension.ExtensionParameter is null) { + wasExtensionFullyInferred = false; return null; } Symbol result; if (extensionMember.IsDefinition) { - NamedTypeSymbol? constructedExtension = inferExtensionTypeArguments(extension, receiverType, compilation); + NamedTypeSymbol? constructedExtension = inferExtensionTypeArguments(extension, receiverType, compilation, out wasExtensionFullyInferred); if (constructedExtension is null) { return null; @@ -1191,12 +1196,15 @@ private static string RawNameToHashString(string rawName) } else { + wasExtensionFullyInferred = true; result = extensionMember; } + ConversionsBase conversions = compilation?.Conversions ?? (ConversionsBase)extensionMember.ContainingAssembly.CorLibrary.TypeConversions; + Debug.Assert(result.ContainingType.ExtensionParameter is not null); var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - Conversion conversion = compilation.Conversions.ConvertExtensionMethodThisArg(parameterType: result.ContainingType.ExtensionParameter.Type, receiverType, ref discardedUseSiteInfo, isMethodGroupConversion: false); + Conversion conversion = conversions.ConvertExtensionMethodThisArg(parameterType: result.ContainingType.ExtensionParameter.Type, receiverType, ref discardedUseSiteInfo, isMethodGroupConversion: false); if (!conversion.Exists) { return null; @@ -1204,10 +1212,11 @@ private static string RawNameToHashString(string rawName) return result; - static NamedTypeSymbol? inferExtensionTypeArguments(NamedTypeSymbol extension, TypeSymbol receiverType, CSharpCompilation compilation) + static NamedTypeSymbol? inferExtensionTypeArguments(NamedTypeSymbol extension, TypeSymbol receiverType, CSharpCompilation? compilation, out bool wasExtensionFullyInferred) { if (extension.Arity == 0) { + wasExtensionFullyInferred = true; return extension; } @@ -1219,12 +1228,14 @@ private static string RawNameToHashString(string rawName) var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; ImmutableArray typeArguments = MethodTypeInferrer.InferTypeArgumentsFromReceiverType(extension, receiverValue, compilation, conversions, ref discardedUseSiteInfo); - if (typeArguments.IsDefault || typeArguments.Any(t => !t.HasType)) + if (typeArguments.IsDefault) { + wasExtensionFullyInferred = false; return null; } - var result = extension.Construct(typeArguments); + ImmutableArray typeArgsForConstruct = fillNotInferredTypeArguments(extension, typeArguments, out wasExtensionFullyInferred); + var result = extension.Construct(typeArgsForConstruct); var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo.Discarded); @@ -1237,6 +1248,20 @@ private static string RawNameToHashString(string rawName) return result; } + + static ImmutableArray fillNotInferredTypeArguments(NamedTypeSymbol extension, ImmutableArray typeArgs, out bool wasFullyInferred) + { + // For the purpose of construction we use original type parameters in place of type arguments that we couldn't infer from the first argument. + wasFullyInferred = typeArgs.All(static t => t.HasType); + if (!wasFullyInferred) + { + return typeArgs.ZipAsArray( + extension.TypeParameters, + (t, tp) => t.HasType ? t : TypeWithAnnotations.Create(tp)); + } + + return typeArgs; + } } } } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index b051a0a83c9c3..7eac7c08b4498 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -38183,6 +38183,114 @@ public static class E Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N;").WithLocation(3, 1)); } + [Fact] + public void LookupSymbols_WasNotFullyInferred() + { + var src = """ + +public static class E +{ + extension(T t) + { + public void M() => throw null; + public int Property => throw null; + } +} +"""; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (7,20): error CS9295: The type parameter `U` is not referenced by either the extension parameter or a parameter of this member + // public int Property => throw null; + Diagnostic(ErrorCode.ERR_UnderspecifiedExtension, "Property").WithArguments("U").WithLocation(7, 20)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var o = ((Compilation)comp).GetSpecialType(SpecialType.System_Object); + AssertEqualAndNoDuplicates(["void E.$B7F0343159FB3A22D67EC9801612841A.M()"], + model.LookupSymbols(position: 0, o, name: "M", includeReducedExtensionMethods: true).ToTestDisplayStrings()); + + AssertEqualAndNoDuplicates(["System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property { get; }"], + model.LookupSymbols(position: 0, o, name: "Property", includeReducedExtensionMethods: true).ToTestDisplayStrings()); + + AssertEqualAndNoDuplicates([ + "void E.$B7F0343159FB3A22D67EC9801612841A.M()", + "System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property { get; }", + .. _objectMembers + ], model.LookupSymbols(position: 0, o, name: null, includeReducedExtensionMethods: true).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/26549")] + public void LookupSymbols_FromBaseInterface_01() + { + var src = """ + +public static class E +{ + extension(I i) + { + public void M(T t) => throw null; + } +} + +public interface I { } +public class C : I { } +"""; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var c = ((Compilation)comp).GlobalNamespace.GetTypeMember("C"); + AssertEqualAndNoDuplicates(["void E.$74EBC78B2187AB07A25EEFC1322000B0.M(System.Int32 t)"], + model.LookupSymbols(position: 0, c, name: "M", includeReducedExtensionMethods: true).ToTestDisplayStrings()); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + AssertEx.Equal("void E.$74EBC78B2187AB07A25EEFC1322000B0.M(System.Int32 t)", + method.ReduceExtensionMember(c).ToTestDisplayString()); + + var extensionMethod = comp.GlobalNamespace.GetTypeMember("E").GetMember("M").GetPublicSymbol(); + AssertEx.Equal("void I.M(System.Int32 t)", extensionMethod.ReduceExtensionMethod(c).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/26549")] + public void LookupSymbols_FromBaseInterface_02() + { + var src = """ + +public static class E +{ + extension(I i) + { + public void M(T t) => throw null; + } +} + +public interface I { } +public class C : I, I { } +"""; + + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + var c = ((Compilation)comp).GlobalNamespace.GetTypeMember("C"); + Assert.Empty(model.LookupSymbols(position: 0, c, name: "M", includeReducedExtensionMethods: true)); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + Assert.Null(method.ReduceExtensionMember(c)); + + var extensionMethod = comp.GlobalNamespace.GetTypeMember("E").GetMember("M").GetPublicSymbol(); + Assert.Null(extensionMethod.ReduceExtensionMethod(c)); + } + [Fact] public void GetMemberGroup_01() { @@ -40682,6 +40790,32 @@ public class C : Base Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(1, 9)); } + [Fact] + public void FunctionType_InstanceReceiver_13() + { + var src = """ +var x = 42.M; + +public static class E +{ + extension(T t) + { + public void M(U u) { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = 42.M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "42.M").WithLocation(1, 9)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, "var x = 42.M"); + Assert.True(model.GetTypeInfo(localDeclaration.Type).Type.IsErrorType()); + } + [Fact] public void FunctionType_ColorColorReceiver_01() { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 49db7576e597b..60c6efb34968a 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -35177,5 +35177,467 @@ public void ExtensionMethod(object o2) { } Diagnostic(ErrorCode.ERR_NameNotInContext, "nonExistent").WithArguments("nonExistent").WithLocation(10, 42) ); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_01() + { + var src = """ +public static class E +{ + extension(T t) where T : class + { + public void M() { } + public int Property { get => 0; set { } } + public void operator +=(T t2) { } + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", method.ToTestDisplayString()); + Assert.Null(method.ReduceExtensionMember(receiverType: null)); + + var substitutedMethod = method.ReduceExtensionMember(systemObject); + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", + substitutedMethod.ToTestDisplayString()); + + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", + substitutedMethod.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var property = extension.GetMember("Property").GetPublicSymbol(); + AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property { get; set; }", property.ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var getter = property.GetMethod; + AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property.get", getter.ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property.get", + getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var setter = property.SetMethod; + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.Property.set", setter.ToTestDisplayString()); + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.Property.set", + setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var op_AdditionAssignment = extension.GetMember("op_AdditionAssignment").GetPublicSymbol(); + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.op_AdditionAssignment(T t2)", op_AdditionAssignment.ToTestDisplayString()); + AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.op_AdditionAssignment(System.Object t2)", + op_AdditionAssignment.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + // broken constraint + INamedTypeSymbol systemInt32 = comp.GetSpecialType(SpecialType.System_Int32).GetPublicSymbol(); + Assert.Null(method.ReduceExtensionMember(systemInt32)); + Assert.Null(property.ReduceExtensionMember(systemInt32)); + Assert.Null(getter.ReduceExtensionMember(systemInt32)); + Assert.Null(setter.ReduceExtensionMember(systemInt32)); + Assert.Null(op_AdditionAssignment.ReduceExtensionMember(systemInt32)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_02() + { + // not fully inferred + var src = """ +public static class E +{ + extension(T t) where T : class + { + public void M(U u) { } + public void operator +=(U u) { } + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + var extensionMethod = comp.GlobalNamespace.GetTypeMember("E").GetMember("M").GetPublicSymbol(); + AssertEx.Equal("void System.Object.M(U u)", extensionMethod.ReduceExtensionMethod(systemObject).ToTestDisplayString()); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + var substitutedMethod = method.ReduceExtensionMember(systemObject); + AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.M(U u)", substitutedMethod.ToTestDisplayString()); + + AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.M(U u)", + substitutedMethod.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var op_AdditionAssignment = extension.GetMember("op_AdditionAssignment").GetPublicSymbol(); + AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.op_AdditionAssignment(U u)", + op_AdditionAssignment.ReduceExtensionMember(systemObject).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_03() + { + // not fully inferred, property + var src = """ +public static class E +{ + extension(T t) + { + public int Property { get => 0; set { } } + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics( + // (5,20): error CS9295: The type parameter `U` is not referenced by either the extension parameter or a parameter of this member + // public int Property { get => 0; set { } } + Diagnostic(ErrorCode.ERR_UnderspecifiedExtension, "Property").WithArguments("U").WithLocation(5, 20)); + + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var property = extension.GetMember("Property").GetPublicSymbol(); + AssertEx.Equal("System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var getter = property.GetMethod; + AssertEx.Equal("System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property.get", + getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + var setter = property.SetMethod; + AssertEx.Equal("void E.$B7F0343159FB3A22D67EC9801612841A.Property.set", + setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_04() + { + // member is generic + var src = """ +public static class E +{ + extension(T t) + { + public void M(U u) { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.M(U u)", + method.ReduceExtensionMember(comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol()).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_05() + { + // void and error types + var src = """ +public static class E +{ + extension(T t) + { + public void M() { } + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + INamedTypeSymbol systemVoid = comp.GetSpecialType(SpecialType.System_Void).GetPublicSymbol(); + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + Assert.Null(method.ReduceExtensionMember(systemVoid)); + + var error = comp.CreateErrorTypeSymbol(null, name: "Error", arity: 0); + Assert.Null(method.ReduceExtensionMember(error)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_06() + { + // non-extension members + var src = """ +public class C +{ + public void M() { } + public int Property => 0; +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + var systemInt32 = comp.GetSpecialType(SpecialType.System_Int32).GetPublicSymbol(); + var extension = comp.GlobalNamespace.GetTypeMember("C"); + + var method = extension.GetMember("M").GetPublicSymbol(); + Assert.Null(method.ReduceExtensionMember(systemInt32)); + + var property = extension.GetMember("Property").GetPublicSymbol(); + Assert.Null(property.ReduceExtensionMember(systemInt32)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_07() + { + // indexer + var src = """ +public static class E +{ + extension(T t) + { + public int this[int i] { get => 0; set { } } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,20): error CS9282: This member is not allowed in an extension block + // public int this[int i] { get => 0; set { } } + Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "this").WithLocation(5, 20)); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var indexer = extension.GetMember("this[]").GetPublicSymbol(); + Assert.Null(indexer.ReduceExtensionMember(comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol())); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_08() + { + // conversion operator + var src = """ +public static class E +{ + extension(T) + { + public static explicit operator int(T t) => 0; + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,41): error CS9282: This member is not allowed in an extension block + // public static explicit operator int(T t) => 0; + Diagnostic(ErrorCode.ERR_ExtensionDisallowsMember, "int").WithLocation(5, 41)); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var op_Explicit = extension.GetMember("op_Explicit").GetPublicSymbol(); + Assert.Null(op_Explicit.ReduceExtensionMember(comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol())); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_09() + { + // non-generic + var src = """ +public static class E +{ + extension(Base b) + { + public void M() { } + public int Property { get => 0; set { } } + public void operator +=(int i) { } + } +} + +public class Base { } +public class Derived : Base { } +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + var property = extension.GetMember("Property").GetPublicSymbol(); + var getter = property.GetMethod; + var setter = property.SetMethod; + var op_AdditionAssignment = extension.GetMember("op_AdditionAssignment").GetPublicSymbol(); + + // object + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + + Assert.Null(method.ReduceExtensionMember(systemObject)); + Assert.Null(property.ReduceExtensionMember(systemObject)); + Assert.Null(getter.ReduceExtensionMember(systemObject)); + Assert.Null(setter.ReduceExtensionMember(systemObject)); + Assert.Null(op_AdditionAssignment.ReduceExtensionMember(systemObject)); + + // Base + INamedTypeSymbol baseType = comp.GlobalNamespace.GetTypeMember("Base").GetPublicSymbol(); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.M()", + method.ReduceExtensionMember(baseType).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property { get; set; }", + property.ReduceExtensionMember(baseType).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.get", + getter.ReduceExtensionMember(baseType).ToTestDisplayString()); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.set", + setter.ReduceExtensionMember(baseType).ToTestDisplayString()); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.op_AdditionAssignment(System.Int32 i)", + op_AdditionAssignment.ReduceExtensionMember(baseType).ToTestDisplayString()); + + // Derived + INamedTypeSymbol derivedType = comp.GlobalNamespace.GetTypeMember("Derived").GetPublicSymbol(); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.M()", + method.ReduceExtensionMember(derivedType).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property { get; set; }", + property.ReduceExtensionMember(derivedType).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.get", + getter.ReduceExtensionMember(derivedType).ToTestDisplayString()); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.set", + setter.ReduceExtensionMember(derivedType).ToTestDisplayString()); + + AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.op_AdditionAssignment(System.Int32 i)", + op_AdditionAssignment.ReduceExtensionMember(derivedType).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_10() + { + // static members + var src = """ +public static class E +{ + extension(object) + { + public static void M() { } + public static int Property { get => 0; set { } } + public static object operator +(object o1, object o2) => throw null; + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + var property = extension.GetMember("Property").GetPublicSymbol(); + var getter = property.GetMethod; + var setter = property.SetMethod; + var op_Addition = extension.GetMember("op_Addition").GetPublicSymbol(); + + // object + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + + AssertEx.Equal("void E.$C43E2675C7BBF9284AF22FB8A9BF0280.M()", + method.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property.get", + getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("void E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property.set", + setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Object E.$C43E2675C7BBF9284AF22FB8A9BF0280.op_Addition(System.Object o1, System.Object o2)", + op_Addition.ReduceExtensionMember(systemObject).ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] + public void ReduceExtensionMember_11() + { + // static members, generic extension block + var src = """ +public static class E +{ + extension(T) + { + public static void M() { } + public static int Property { get => 0; set { } } + public static T operator +(T t1, T t2) => throw null; + } +} +"""; + var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); + comp.VerifyEmitDiagnostics(); + + var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); + var method = extension.GetMember("M").GetPublicSymbol(); + var property = extension.GetMember("Property").GetPublicSymbol(); + var getter = property.GetMethod; + var setter = property.SetMethod; + var op_Addition = extension.GetMember("op_Addition").GetPublicSymbol(); + + // object + INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); + + AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.M()", + method.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$8048A6C8BE30A622530249B904B537EB.Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Int32 E.$8048A6C8BE30A622530249B904B537EB.Property.get", + getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.Property.set", + setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + + AssertEx.Equal("System.Object E.$8048A6C8BE30A622530249B904B537EB.op_Addition(System.Object t1, System.Object t2)", + op_Addition.ReduceExtensionMember(systemObject).ToTestDisplayString()); + } + + [Fact] + public void ReduceExtensionMember_12() + { + // probe handling of wasExtensionFullyinferred + var src = """ +_ = object.M; +_ = nameof(object.M); + +static class E +{ + extension(T) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,1): error CS8183: Cannot infer the type of implicitly-typed discard. + // _ = object.M; + Diagnostic(ErrorCode.ERR_DiscardTypeInferenceFailed, "_").WithLocation(1, 1), + // (2,12): error CS8093: Extension method groups are not allowed as an argument to 'nameof'. + // _ = nameof(object.M); + Diagnostic(ErrorCode.ERR_NameofExtensionMethod, "object.M").WithLocation(2, 12)); + } + + [Fact] + public void ReduceExtensionMember_13() + { + // probe handling of wasExtensionFullyinferred + var src = """ +_ = object.M; +_ = nameof(object.M); + +static class E +{ + extension(T) + { + public static void M() { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,1): error CS8183: Cannot infer the type of implicitly-typed discard. + // _ = object.M; + Diagnostic(ErrorCode.ERR_DiscardTypeInferenceFailed, "_").WithLocation(1, 1), + // (2,12): error CS8093: Extension method groups are not allowed as an argument to 'nameof'. + // _ = nameof(object.M); + Diagnostic(ErrorCode.ERR_NameofExtensionMethod, "object.M").WithLocation(2, 12)); + } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index fb0ee6a5ae61e..fc5b0a225448b 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -15,10 +15,12 @@ Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.get -> bool Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.EmitFieldRva.init -> void Microsoft.CodeAnalysis.IMethodSymbol.AssociatedExtensionImplementation.get -> Microsoft.CodeAnalysis.IMethodSymbol? Microsoft.CodeAnalysis.IMethodSymbol.IsIterator.get -> bool +Microsoft.CodeAnalysis.IMethodSymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol! receiverType) -> Microsoft.CodeAnalysis.IMethodSymbol? Microsoft.CodeAnalysis.INamedTypeSymbol.ExtensionGroupingName.get -> string? Microsoft.CodeAnalysis.INamedTypeSymbol.ExtensionMarkerName.get -> string? Microsoft.CodeAnalysis.INamedTypeSymbol.ExtensionParameter.get -> Microsoft.CodeAnalysis.IParameterSymbol? Microsoft.CodeAnalysis.INamedTypeSymbol.IsExtension.get -> bool +Microsoft.CodeAnalysis.IPropertySymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol! receiverType) -> Microsoft.CodeAnalysis.IPropertySymbol? static readonly Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions.Default -> Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions Microsoft.CodeAnalysis.IEventSymbol.IsPartialDefinition.get -> bool Microsoft.CodeAnalysis.IEventSymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IEventSymbol? diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index 55a66b31e3f44..fe11ecf71ff1a 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -196,6 +196,12 @@ public interface IMethodSymbol : ISymbol /// IMethodSymbol? ReduceExtensionMethod(ITypeSymbol receiverType); + /// + /// If this is a (new) extension method that can be applied to a receiver of the given type, + /// returns the method symbol in the substituted extension for that receiver type. Otherwise, returns null. + /// + IMethodSymbol? ReduceExtensionMember(ITypeSymbol receiverType); + /// /// Returns interface methods explicitly implemented by this method. /// diff --git a/src/Compilers/Core/Portable/Symbols/IPropertySymbol.cs b/src/Compilers/Core/Portable/Symbols/IPropertySymbol.cs index 12735dd442946..cb178a50e1d54 100644 --- a/src/Compilers/Core/Portable/Symbols/IPropertySymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IPropertySymbol.cs @@ -127,5 +127,11 @@ public interface IPropertySymbol : ISymbol /// Returns true if this is a partial definition part. Otherwise false. /// bool IsPartialDefinition { get; } + + /// + /// If this is an extension property that can be applied to a receiver of the given type, + /// returns the property symbol in the substituted extension for that receiver type. Otherwise, returns null. + /// + IPropertySymbol? ReduceExtensionMember(ITypeSymbol receiverType); } } diff --git a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb index c85130b736d2b..898e7c14a07b6 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/MethodSymbol.vb @@ -826,6 +826,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Return ReduceExtensionMethod(instanceType, proximity:=0, useSiteInfo, languageVersion) End Function + Public Function ReduceExtensionMember(receiverType As ITypeSymbol) As IMethodSymbol Implements IMethodSymbol.ReduceExtensionMember + Return Nothing + End Function + ''' ''' Proximity level of a reduced extension method. ''' diff --git a/src/Compilers/VisualBasic/Portable/Symbols/PropertySymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/PropertySymbol.vb index 29ecdc870a5fe..dace4776542d4 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/PropertySymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/PropertySymbol.vb @@ -691,6 +691,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols Return visitor.VisitProperty(Me) End Function + Public Function ReduceExtensionMember(receiverType As ITypeSymbol) As IPropertySymbol Implements IPropertySymbol.ReduceExtensionMember + Return Nothing + End Function #End Region End Class diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/ExtensionMethods/ExtensionMethodTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/ExtensionMethods/ExtensionMethodTests.vb index edf43ae8aa6a6..f4bf61e4b946f 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/ExtensionMethods/ExtensionMethodTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/ExtensionMethods/ExtensionMethodTests.vb @@ -2563,6 +2563,30 @@ End Module Dim reducedMethodOnVoid = extensionMethod.ReduceExtensionMethod(compilation.GetSpecialType(SpecialType.System_Void)) Assert.Null(reducedMethodOnVoid) End Sub + + + Public Sub ReduceExtensionMember_01() + Dim compilation = CreateCompilation( + + +) + + compilation.VerifyEmitDiagnostics() + + Dim systemObject As NamedTypeSymbol = compilation.GetSpecialType(SpecialType.System_Object) + Dim method = DirectCast(compilation.GlobalNamespace.GetTypeMember("E").GetMember(Of MethodSymbol)("Method"), IMethodSymbol) + Assert.NotNull(method) + Assert.Null(method.ReduceExtensionMember(systemObject)) + + Dim prop = DirectCast(compilation.GlobalNamespace.GetTypeMember("E").GetMember(Of PropertySymbol)("Prop"), IPropertySymbol) + Assert.Null(prop.ReduceExtensionMember(systemObject)) + End Sub End Class End Namespace diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs index 61fafff3af990..25b0776e9b310 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedMethodSymbol.cs @@ -116,10 +116,10 @@ public DllImportData GetDllImportData() => _symbol.GetDllImportData(); public IMethodSymbol ReduceExtensionMethod(ITypeSymbol receiverType) - { - // This implementation feels incorrect! - return _symbol.ReduceExtensionMethod(receiverType); - } + => throw new System.NotImplementedException(); + + public IMethodSymbol ReduceExtensionMember(ITypeSymbol receiverType) + => throw new System.NotImplementedException(); public IMethodSymbol AssociatedExtensionImplementation => null; diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedPropertySymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedPropertySymbol.cs index 27fd29f8b7fd6..f00a82ab1ac55 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedPropertySymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedPropertySymbol.cs @@ -72,5 +72,8 @@ public ImmutableArray ExplicitInterfaceImplementations public IPropertySymbol PartialImplementationPart => _symbol.PartialImplementationPart; public bool IsPartialDefinition => _symbol.IsPartialDefinition; + + public IPropertySymbol ReduceExtensionMember(ITypeSymbol receiverType) + => throw new System.NotImplementedException(); } } diff --git a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt index 7e9430fccad7d..c3da974bae4db 100644 --- a/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt +++ b/src/Tools/SemanticSearch/ReferenceAssemblies/Apis/Microsoft.CodeAnalysis.txt @@ -1228,6 +1228,7 @@ Microsoft.CodeAnalysis.IMethodSymbol.Construct(System.Collections.Immutable.Immu Microsoft.CodeAnalysis.IMethodSymbol.GetDllImportData Microsoft.CodeAnalysis.IMethodSymbol.GetReturnTypeAttributes Microsoft.CodeAnalysis.IMethodSymbol.GetTypeInferredDuringReduction(Microsoft.CodeAnalysis.ITypeParameterSymbol) +Microsoft.CodeAnalysis.IMethodSymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol) Microsoft.CodeAnalysis.IMethodSymbol.ReduceExtensionMethod(Microsoft.CodeAnalysis.ITypeSymbol) Microsoft.CodeAnalysis.IMethodSymbol.get_Arity Microsoft.CodeAnalysis.IMethodSymbol.get_AssociatedAnonymousDelegate @@ -1379,6 +1380,7 @@ Microsoft.CodeAnalysis.IPointerTypeSymbol.get_CustomModifiers Microsoft.CodeAnalysis.IPointerTypeSymbol.get_PointedAtType Microsoft.CodeAnalysis.IPreprocessingSymbol Microsoft.CodeAnalysis.IPropertySymbol +Microsoft.CodeAnalysis.IPropertySymbol.ReduceExtensionMember(Microsoft.CodeAnalysis.ITypeSymbol) Microsoft.CodeAnalysis.IPropertySymbol.get_ExplicitInterfaceImplementations Microsoft.CodeAnalysis.IPropertySymbol.get_GetMethod Microsoft.CodeAnalysis.IPropertySymbol.get_IsIndexer diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs index db6df0f49a519..63c696b27e2f1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationAbstractMethodSymbol.cs @@ -117,4 +117,7 @@ public IMethodSymbol Construct(ImmutableArray typeArguments, Immuta public DllImportData GetDllImportData() => null; + + public IMethodSymbol ReduceExtensionMember(ITypeSymbol receiverType) + => throw new System.NotImplementedException(); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationPropertySymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationPropertySymbol.cs index 1c6501d043bf7..4c2d43578a020 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationPropertySymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationPropertySymbol.cs @@ -86,4 +86,7 @@ public override TResult Accept(SymbolVisitor null; public bool IsPartialDefinition => false; + + public IPropertySymbol ReduceExtensionMember(ITypeSymbol receiverType) + => throw new System.NotImplementedException(); } From 8f90cc43159aba6be320dd197cc5d791cd539138 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 16 Oct 2025 14:09:58 -0700 Subject: [PATCH 2/4] Address feedback --- src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs index fe11ecf71ff1a..84c578c99660f 100644 --- a/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/IMethodSymbol.cs @@ -197,7 +197,7 @@ public interface IMethodSymbol : ISymbol IMethodSymbol? ReduceExtensionMethod(ITypeSymbol receiverType); /// - /// If this is a (new) extension method that can be applied to a receiver of the given type, + /// If this is a method of an extension block that can be applied to a receiver of the given type, /// returns the method symbol in the substituted extension for that receiver type. Otherwise, returns null. /// IMethodSymbol? ReduceExtensionMember(ITypeSymbol receiverType); From 609150c36c8b58fbff33e743f6870834b3f66d37 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 16 Oct 2025 14:11:44 -0700 Subject: [PATCH 3/4] Rename internal API --- src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs | 2 +- src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs | 2 +- .../CSharp/Portable/Compilation/CSharpSemanticModel.cs | 2 +- .../CSharp/Portable/Symbols/MemberSymbolExtensions.cs | 4 ++-- .../CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs | 2 +- .../CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs | 2 +- .../Symbols/Source/SourceNamedTypeSymbol_Extension.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 6405ea6ba3e09..b96990be48cbc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -8773,7 +8773,7 @@ static MethodGroupResolution resolveMethods( inapplicable = true; } else if (method.GetIsNewExtensionMember() - && SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, method, receiverType, wasExtensionFullyInferred: out _) is null) + && SourceNamedTypeSymbol.ReduceExtensionMember(binder.Compilation, method, receiverType, wasExtensionFullyInferred: out _) is null) { inapplicable = true; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index 3dd663baa50eb..0258c2a591e13 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -1585,7 +1585,7 @@ private void CheckWhatCandidatesWeHave( else { Debug.Assert(symbol.GetIsNewExtensionMember()); - if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(this.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) + if (SourceNamedTypeSymbol.ReduceExtensionMember(this.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) { if (compatibleSubstitutedMember.IsStatic) { diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 854bf9d594483..80d2ab16fedfb 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -1681,7 +1681,7 @@ private ImmutableArray LookupSymbolsInternal( else { Debug.Assert(symbol.GetIsNewExtensionMember()); - if (SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) + if (SourceNamedTypeSymbol.ReduceExtensionMember(binder.Compilation, symbol, receiverType, wasExtensionFullyInferred: out _) is { } compatibleSubstitutedMember) { results.Add(compatibleSubstitutedMember.GetPublicSymbol()); } diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 17ca582bdb6ba..d3ce7880b6990 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -281,7 +281,7 @@ internal static TMember ConstructIncludingExtension(this TMember member else { Debug.Assert(method.GetIsNewExtensionMember()); - constructed = (MethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType, out bool wasExtensionFullyInferred); + constructed = (MethodSymbol?)SourceNamedTypeSymbol.ReduceExtensionMember(compilation, constructed, receiverType, out bool wasExtensionFullyInferred); if (checkFullyInferred && (!wasExtensionFullyInferred || (constructed?.IsGenericMethod == true && typeArguments.IsDefaultOrEmpty))) { return null; @@ -296,7 +296,7 @@ internal static TMember ConstructIncludingExtension(this TMember member // infer type arguments based off the receiver type if needed, check applicability Debug.Assert(receiverType is not null); Debug.Assert(property.GetIsNewExtensionMember()); - var result = (PropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType, wasExtensionFullyInferred: out bool wasFullyInferred); + var result = (PropertySymbol?)SourceNamedTypeSymbol.ReduceExtensionMember(compilation, property, receiverType, wasExtensionFullyInferred: out bool wasFullyInferred); if (checkFullyInferred && !wasFullyInferred) { return null; diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs index 10d567b679957..71bc607ac19f8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/MethodSymbol.cs @@ -214,7 +214,7 @@ ITypeSymbol IMethodSymbol.GetTypeInferredDuringReduction(ITypeParameterSymbol re if (_underlying.GetIsNewExtensionMember() && SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(_underlying)) { var csharpReceiver = receiverType.EnsureCSharpSymbolOrNull(nameof(receiverType)); - return (IMethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); + return (IMethodSymbol?)SourceNamedTypeSymbol.ReduceExtensionMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); } return null; diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs index 7047fdaf03d7c..06e763f0e26d8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/PropertySymbol.cs @@ -121,7 +121,7 @@ ImmutableArray IPropertySymbol.RefCustomModifiers if (_underlying.GetIsNewExtensionMember() && SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(_underlying)) { var csharpReceiver = receiverType.EnsureCSharpSymbolOrNull(nameof(receiverType)); - return (IPropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); + return (IPropertySymbol?)SourceNamedTypeSymbol.ReduceExtensionMember(compilation: null, _underlying, csharpReceiver, wasExtensionFullyInferred: out _).GetPublicSymbol(); } return null; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs index 4e2c24af2ebe7..499f47a83e7fe 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol_Extension.cs @@ -1172,7 +1172,7 @@ private static string RawNameToHashString(string rawName) /// Given a receiver type, check if we can infer type arguments for the extension block and check for compatibility. /// If that is successful, return the substituted extension member and whether the extension block was fully inferred. /// - internal static Symbol? GetCompatibleSubstitutedMember(CSharpCompilation? compilation, Symbol extensionMember, TypeSymbol receiverType, out bool wasExtensionFullyInferred) + internal static Symbol? ReduceExtensionMember(CSharpCompilation? compilation, Symbol extensionMember, TypeSymbol receiverType, out bool wasExtensionFullyInferred) { Debug.Assert(extensionMember.GetIsNewExtensionMember()); From db8cc4111e8dc16115ab71d6a9c4b542cc56c54c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 16 Oct 2025 14:50:18 -0700 Subject: [PATCH 4/4] Use "extension" symbol display --- .../Test/Emit3/Semantics/ExtensionTests2.cs | 150 +++++++++--------- 1 file changed, 79 insertions(+), 71 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 60c6efb34968a..90597c1bc3b7e 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -35195,40 +35195,41 @@ public void M() { } var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); comp.VerifyEmitDiagnostics(); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", method.ToTestDisplayString()); + AssertEx.Equal("void E.extension(T).M()", method.ToDisplayString(displayOptions)); Assert.Null(method.ReduceExtensionMember(receiverType: null)); var substitutedMethod = method.ReduceExtensionMember(systemObject); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", - substitutedMethod.ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M()", + substitutedMethod.ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.M()", - substitutedMethod.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M()", + substitutedMethod.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var property = extension.GetMember("Property").GetPublicSymbol(); - AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property { get; set; }", property.ToTestDisplayString()); - AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property { get; set; }", - property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(T).Property { get; set; }", property.ToDisplayString(displayOptions)); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var getter = property.GetMethod; - AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property.get", getter.ToTestDisplayString()); - AssertEx.Equal("System.Int32 E.$66F77D1E46F965A5B22D4932892FA78B.Property.get", - getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(T).Property.get", getter.ToDisplayString(displayOptions)); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property.get", + getter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var setter = property.SetMethod; - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.Property.set", setter.ToTestDisplayString()); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.Property.set", - setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(T).Property.set", setter.ToDisplayString(displayOptions)); + AssertEx.Equal("void E.extension(System.Object).Property.set", + setter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var op_AdditionAssignment = extension.GetMember("op_AdditionAssignment").GetPublicSymbol(); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.op_AdditionAssignment(T t2)", op_AdditionAssignment.ToTestDisplayString()); - AssertEx.Equal("void E.$66F77D1E46F965A5B22D4932892FA78B.op_AdditionAssignment(System.Object t2)", - op_AdditionAssignment.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(T).operator +=(T t2)", op_AdditionAssignment.ToDisplayString(displayOptions)); + AssertEx.Equal("void E.extension(System.Object).operator +=(System.Object t2)", + op_AdditionAssignment.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); // broken constraint INamedTypeSymbol systemInt32 = comp.GetSpecialType(SpecialType.System_Int32).GetPublicSymbol(); @@ -35256,21 +35257,22 @@ public void M(U u) { } var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); comp.VerifyEmitDiagnostics(); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); var extensionMethod = comp.GlobalNamespace.GetTypeMember("E").GetMember("M").GetPublicSymbol(); - AssertEx.Equal("void System.Object.M(U u)", extensionMethod.ReduceExtensionMethod(systemObject).ToTestDisplayString()); + AssertEx.Equal("void System.Object.M(U u)", extensionMethod.ReduceExtensionMethod(systemObject).ToDisplayString(displayOptions)); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); var substitutedMethod = method.ReduceExtensionMember(systemObject); - AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.M(U u)", substitutedMethod.ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M(U u)", substitutedMethod.ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.M(U u)", - substitutedMethod.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M(U u)", + substitutedMethod.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var op_AdditionAssignment = extension.GetMember("op_AdditionAssignment").GetPublicSymbol(); - AssertEx.Equal("void E.$EFE563D6389F084705E4ACFD3AFA030A.op_AdditionAssignment(U u)", - op_AdditionAssignment.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).operator +=(U u)", + op_AdditionAssignment.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] @@ -35292,20 +35294,21 @@ public static class E // public int Property { get => 0; set { } } Diagnostic(ErrorCode.ERR_UnderspecifiedExtension, "Property").WithArguments("U").WithLocation(5, 20)); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var property = extension.GetMember("Property").GetPublicSymbol(); - AssertEx.Equal("System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property { get; set; }", - property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var getter = property.GetMethod; - AssertEx.Equal("System.Int32 E.$B7F0343159FB3A22D67EC9801612841A.Property.get", - getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property.get", + getter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); var setter = property.SetMethod; - AssertEx.Equal("void E.$B7F0343159FB3A22D67EC9801612841A.Property.set", - setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).Property.set", + setter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] @@ -35323,10 +35326,12 @@ public void M(U u) { } """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics(); + + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); - AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.M(U u)", - method.ReduceExtensionMember(comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol()).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M(U u)", + method.ReduceExtensionMember(comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol()).ToDisplayString(displayOptions)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] @@ -35447,6 +35452,7 @@ public class Derived : Base { } var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); comp.VerifyEmitDiagnostics(); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); var property = extension.GetMember("Property").GetPublicSymbol(); @@ -35466,38 +35472,38 @@ public class Derived : Base { } // Base INamedTypeSymbol baseType = comp.GlobalNamespace.GetTypeMember("Base").GetPublicSymbol(); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.M()", - method.ReduceExtensionMember(baseType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).M()", + method.ReduceExtensionMember(baseType).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property { get; set; }", - property.ReduceExtensionMember(baseType).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(Base).Property { get; set; }", + property.ReduceExtensionMember(baseType).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.get", - getter.ReduceExtensionMember(baseType).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(Base).Property.get", + getter.ReduceExtensionMember(baseType).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.set", - setter.ReduceExtensionMember(baseType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).Property.set", + setter.ReduceExtensionMember(baseType).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.op_AdditionAssignment(System.Int32 i)", - op_AdditionAssignment.ReduceExtensionMember(baseType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).operator +=(System.Int32 i)", + op_AdditionAssignment.ReduceExtensionMember(baseType).ToDisplayString(displayOptions)); // Derived INamedTypeSymbol derivedType = comp.GlobalNamespace.GetTypeMember("Derived").GetPublicSymbol(); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.M()", - method.ReduceExtensionMember(derivedType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).M()", + method.ReduceExtensionMember(derivedType).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property { get; set; }", - property.ReduceExtensionMember(derivedType).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(Base).Property { get; set; }", + property.ReduceExtensionMember(derivedType).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.get", - getter.ReduceExtensionMember(derivedType).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(Base).Property.get", + getter.ReduceExtensionMember(derivedType).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.Property.set", - setter.ReduceExtensionMember(derivedType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).Property.set", + setter.ReduceExtensionMember(derivedType).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$76A32DFFBBF61DFEA0C27B13F12F6EFB.op_AdditionAssignment(System.Int32 i)", - op_AdditionAssignment.ReduceExtensionMember(derivedType).ToTestDisplayString()); + AssertEx.Equal("void E.extension(Base).operator +=(System.Int32 i)", + op_AdditionAssignment.ReduceExtensionMember(derivedType).ToDisplayString(displayOptions)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] @@ -35518,6 +35524,7 @@ public static void M() { } var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); comp.VerifyEmitDiagnostics(); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); var property = extension.GetMember("Property").GetPublicSymbol(); @@ -35528,20 +35535,20 @@ public static void M() { } // object INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); - AssertEx.Equal("void E.$C43E2675C7BBF9284AF22FB8A9BF0280.M()", - method.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M()", + method.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property { get; set; }", - property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property.get", - getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property.get", + getter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$C43E2675C7BBF9284AF22FB8A9BF0280.Property.set", - setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).Property.set", + setter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Object E.$C43E2675C7BBF9284AF22FB8A9BF0280.op_Addition(System.Object o1, System.Object o2)", - op_Addition.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Object E.extension(System.Object).operator +(System.Object o1, System.Object o2)", + op_Addition.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80273")] @@ -35562,6 +35569,7 @@ public static void M() { } var comp = CreateCompilation([src, CompilerFeatureRequiredAttribute]); comp.VerifyEmitDiagnostics(); + var displayOptions = SymbolDisplayFormat.TestFormat.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.None); var extension = comp.GlobalNamespace.GetTypeMember("E").GetTypeMembers().Single(); var method = extension.GetMember("M").GetPublicSymbol(); var property = extension.GetMember("Property").GetPublicSymbol(); @@ -35572,20 +35580,20 @@ public static void M() { } // object INamedTypeSymbol systemObject = comp.GetSpecialType(SpecialType.System_Object).GetPublicSymbol(); - AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.M()", - method.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).M()", + method.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$8048A6C8BE30A622530249B904B537EB.Property { get; set; }", - property.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property { get; set; }", + property.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Int32 E.$8048A6C8BE30A622530249B904B537EB.Property.get", - getter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Int32 E.extension(System.Object).Property.get", + getter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("void E.$8048A6C8BE30A622530249B904B537EB.Property.set", - setter.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("void E.extension(System.Object).Property.set", + setter.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); - AssertEx.Equal("System.Object E.$8048A6C8BE30A622530249B904B537EB.op_Addition(System.Object t1, System.Object t2)", - op_Addition.ReduceExtensionMember(systemObject).ToTestDisplayString()); + AssertEx.Equal("System.Object E.extension(System.Object).operator +(System.Object t1, System.Object t2)", + op_Addition.ReduceExtensionMember(systemObject).ToDisplayString(displayOptions)); } [Fact]