From 0195e15e7ac9683cc7a4821ac1bcba43b6c9133f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 20 Mar 2025 11:17:19 -0700 Subject: [PATCH 1/2] Extensions: account for new extensions in function type scenarios --- .../Portable/Binder/Binder_Expressions.cs | 74 +- .../Compilation/CSharpSemanticModel.cs | 91 +-- .../Symbols/MemberSymbolExtensions.cs | 94 +++ .../Test/Emit3/Semantics/ExtensionTests.cs | 692 +++++++++++++++++- 4 files changed, 835 insertions(+), 116 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index a16cb0ef9c6ba..08615e500bcb8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -10988,6 +10988,9 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) useParams = false; MethodSymbol? foundMethod = null; var typeArguments = node.TypeArgumentsOpt; + int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length; + + // 1. instance methods if (node.ResultKind == LookupResultKind.Viable) { var methods = ArrayBuilder.GetInstance(capacity: node.Methods.Length); @@ -10995,6 +10998,8 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) { switch (node.ReceiverOpt) { + case BoundTypeOrValueExpression: + break; case BoundTypeExpression: case null: // if `using static Class` is in effect, the receiver is missing if (!memberMethod.IsStatic) continue; @@ -11006,7 +11011,6 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) break; } - int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length; if (memberMethod.Arity != arity) { // We have no way of inferring type arguments, so if the given type arguments @@ -11047,52 +11051,63 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) } } - if (node.ReceiverOpt is not BoundTypeExpression && node.SearchExtensions) + // 2. extensions + if (node.SearchExtensions) { - var receiver = node.ReceiverOpt!; - var methodGroup = MethodGroup.GetInstance(); + Debug.Assert(node.ReceiverOpt!.Type is not null); // extensions are only considered on member access + + BoundExpression receiver = node.ReceiverOpt; + LookupOptions options = arity == 0 ? LookupOptions.AllMethodsOnArityZero : LookupOptions.Default; + var singleLookupResults = ArrayBuilder.GetInstance(); + CompoundUseSiteInfo discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + foreach (var scope in new ExtensionScopes(this)) { - methodGroup.Clear(); - PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, typeArguments, BindingDiagnosticBag.Discarded); // PROTOTYPE account for new extension members - var methods = ArrayBuilder.GetInstance(capacity: methodGroup.Methods.Count); - foreach (var extensionMethod in methodGroup.Methods) - { - var substituted = typeArguments.IsDefaultOrEmpty ? extensionMethod : extensionMethod.Construct(typeArguments); - - var reduced = substituted.ReduceExtensionMethod(receiver.Type, Compilation, out bool wasFullyInferred); - if (reduced is null) - { - // Extension method was not applicable - continue; - } + singleLookupResults.Clear(); + scope.Binder.EnumerateAllExtensionMembersInSingleBinder(singleLookupResults, node.Name, arity, options, originalBinder: this, ref discardedUseSiteInfo, ref discardedUseSiteInfo); - if (!wasFullyInferred) + var methods = ArrayBuilder.GetInstance(capacity: singleLookupResults.Count); + foreach (SingleLookupResult singleLookupResult in singleLookupResults) + { + // Remove static/instance mismatches + Symbol extensionMember = singleLookupResult.Symbol; + bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic; + switch (node.ReceiverOpt) { - continue; + case BoundTypeOrValueExpression: + break; + case BoundTypeExpression: + if (!memberCountsAsStatic) continue; + break; + default: + if (memberCountsAsStatic) continue; + break; } - if (!satisfiesConstraintChecks(reduced)) + // Note: we only care about methods since we're already decided this is a method group (ie. not resolving to some other kind of extension member) + if (extensionMember is MethodSymbol method) { - continue; + var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true); + if (substituted is not null) + { + methods.Add(substituted); + } } - - methods.Add(reduced); } if (!OverloadResolution.FilterMethodsForUniqueSignature(methods, out useParams)) { + singleLookupResults.Free(); methods.Free(); - methodGroup.Free(); return null; } - foreach (var reduced in methods) + foreach (var method in methods) { - if (!isCandidateUnique(ref foundMethod, reduced)) + if (!isCandidateUnique(ref foundMethod, method)) { + singleLookupResults.Free(); methods.Free(); - methodGroup.Free(); useParams = false; return null; } @@ -11102,11 +11117,12 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) if (foundMethod is not null) { - methodGroup.Free(); + singleLookupResults.Free(); return foundMethod; } } - methodGroup.Free(); + + singleLookupResults.Free(); } useParams = false; diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 2de6a80574d74..c0c8c6d477a92 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -4683,92 +4683,23 @@ private static bool AddReducedAndFilteredSymbol( TypeSymbol receiverType, CSharpCompilation compilation) { - if (member is MethodSymbol method) + Symbol? substitutedMember = member.GetReducedAndFilteredSymbol(typeArguments, receiverType, compilation, checkFullyInferred: false); + if (substitutedMember is null) { - MethodSymbol constructedMethod; - if (!typeArguments.IsDefaultOrEmpty && method.GetMemberArityIncludingExtension() == typeArguments.Length) - { - constructedMethod = method.ConstructIncludingExtension(typeArguments); - Debug.Assert((object)constructedMethod != null); - - if (!checkConstraintsIncludingExtension(constructedMethod, compilation, method.ContainingAssembly.CorLibrary.TypeConversions)) - { - return false; - } - } - else - { - constructedMethod = method; - } - - if ((object)receiverType != null) - { - if (method.IsExtensionMethod) - { - constructedMethod = constructedMethod.ReduceExtensionMethod(receiverType, compilation); - } - else - { - Debug.Assert(method.GetIsNewExtensionMember()); - constructedMethod = (MethodSymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructedMethod, receiverType)!; - } - - if ((object)constructedMethod == null) - { - return false; - } - } - - // Don't add exact duplicates. - if (filteredMembers.Contains(constructedMethod)) - { - return false; - } - - members.Add(member); - filteredMembers.Add(constructedMethod); - return true; - } - else if (member is PropertySymbol property) - { - Debug.Assert(receiverType is not null); - Debug.Assert(property.GetIsNewExtensionMember()); - var constructedProperty = (PropertySymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType)!; - - if (constructedProperty is null) - { - return false; - } - - members.Add(member); - filteredMembers.Add(constructedProperty); - return true; + return false; } - throw ExceptionUtilities.UnexpectedValue(member.Kind); - - static bool checkConstraintsIncludingExtension(MethodSymbol symbol, CSharpCompilation compilation, TypeConversions conversions) + // Don't add exact duplicates. + if (filteredMembers.Contains(substitutedMember)) { - var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, - NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo.Discarded); - - bool success = true; - - if (symbol.GetIsNewExtensionMember()) - { - NamedTypeSymbol extensionDeclaration = symbol.ContainingType; - success = extensionDeclaration.CheckConstraints(constraintArgs); - } - - if (success) - { - success = symbol.CheckConstraints(constraintArgs); - } - - return success; + return false; } -#nullable disable + + members.Add(member); + filteredMembers.Add(substitutedMember); + return true; } +#nullable disable private static void MergeReducedAndFilteredSymbol( ArrayBuilder members, diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 5991c132e2d90..47071f89fa4e8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -11,6 +11,7 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -154,6 +155,99 @@ internal static TMember ConstructIncludingExtension(this TMember member throw ExceptionUtilities.UnexpectedValue(member); } + // For lookup APIs in the semantic model, we can return symbols that aren't fully inferred. + // But for function type inference, if the symbol isn't fully inferred with the information we have (the receiver and any explicit type arguments) + // then we won't return it. + internal static Symbol? GetReducedAndFilteredSymbol(this Symbol member, ImmutableArray typeArguments, TypeSymbol receiverType, CSharpCompilation compilation, bool checkFullyInferred) + { + if (member is MethodSymbol method) + { + // 1. construct with explicit type arguments if provided + MethodSymbol constructed; + if (!typeArguments.IsDefaultOrEmpty && method.GetMemberArityIncludingExtension() == typeArguments.Length) + { + constructed = method.ConstructIncludingExtension(typeArguments); + Debug.Assert((object)constructed != null); + + if (!checkConstraintsIncludingExtension(constructed, compilation, method.ContainingAssembly.CorLibrary.TypeConversions)) + { + return null; + } + } + else + { + constructed = method; + } + + // 2. infer type arguments based on the receiver type if needed, check applicability, reduce symbol (for classic extension methods), check whether fully inferred + if ((object)receiverType != null) + { + if (method.IsExtensionMethod) + { + constructed = constructed.ReduceExtensionMethod(receiverType, compilation, out bool wasFullyInferred); + + if (checkFullyInferred && !wasFullyInferred) + { + return null; + } + } + else + { + Debug.Assert(method.GetIsNewExtensionMember()); + constructed = (MethodSymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType)!; + + if (checkFullyInferred && constructed.IsGenericMethod && typeArguments.IsDefaultOrEmpty) + { + return null; + } + } + + if ((object)constructed == null) + { + return null; + } + } + + return constructed; + } + else if (member is PropertySymbol property) + { + // infer type arguments based off the receiver type if needed, check applicability + Debug.Assert(receiverType is not null); + Debug.Assert(property.GetIsNewExtensionMember()); + var constructedProperty = (PropertySymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType)!; + + if (constructedProperty is null) + { + return null; + } + + return constructedProperty; + } + + throw ExceptionUtilities.UnexpectedValue(member.Kind); + + static bool checkConstraintsIncludingExtension(MethodSymbol symbol, CSharpCompilation compilation, TypeConversions conversions) + { + var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, + NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo.Discarded); + + bool success = true; + + if (symbol.GetIsNewExtensionMember()) + { + NamedTypeSymbol extensionDeclaration = symbol.ContainingType; + success = extensionDeclaration.CheckConstraints(constraintArgs); + } + + if (success) + { + success = symbol.CheckConstraints(constraintArgs); + } + + return success; + } + } #nullable disable /// diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index f70249ff18948..743d80ed07cdf 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -16152,7 +16152,7 @@ static class E Assert.Equal([], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); // PROTOTYPE handle GetMemberGroup on a property access } - [Fact(Skip = "PROTOTYPE function type")] + [Fact] public void ResolveAll_Instance_InferredVariable_InnerExtensionMethodVsOuterInvocableExtensionProperty() { var src = """ @@ -16182,15 +16182,13 @@ static class E } """; var comp = CreateCompilation(src, options: TestOptions.DebugExe); - comp.VerifyEmitDiagnostics(); - // PROTOTYPE metadata is undone - //CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntax(tree, "new object().M"); Assert.Equal("void System.Object.M(System.Int32 i)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); - Assert.Equal(["void System.Object.M(System.Int32 i)"], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); // PROTOTYPE should include the extension property + Assert.Equal(["void System.Object.M(System.Int32 i)", "System.Action E.<>E__0.M { get; }"], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } [Fact] @@ -28411,11 +28409,10 @@ public static void M2() { } comp = CreateCompilation(srcCompat, references: [libRef]); comp.VerifyEmitDiagnostics(); - // PROTOTYPE function type not yet supported var src = """ object.M2(); System.Action a = object.M2; -//var x = object.M2; +var x = object.M2; _ = object.P; """; @@ -28427,6 +28424,9 @@ public static void M2() { } // (2,26): error CS0117: 'object' does not contain a definition for 'M2' // System.Action a = object.M2; Diagnostic(ErrorCode.ERR_NoSuchMember, "M2").WithArguments("object", "M2").WithLocation(2, 26), + // (3,16): error CS0117: 'object' does not contain a definition for 'M2' + // var x = object.M2; + Diagnostic(ErrorCode.ERR_NoSuchMember, "M2").WithArguments("object", "M2").WithLocation(3, 16), // (5,12): error CS0117: 'object' does not contain a definition for 'P' // _ = object.P; Diagnostic(ErrorCode.ERR_NoSuchMember, "P").WithArguments("object", "P").WithLocation(5, 12)); @@ -29049,4 +29049,682 @@ public void M() // base.ToString(); Diagnostic(ErrorCode.ERR_BaseInBadContext, "base").WithLocation(7, 13)); } + + [Fact] + public void FunctionType_TypeReceiver_01() + { + var src = """ +var x = int.M; +x(); + +public static class E +{ + extension(T t) + { + public static void M() { System.Console.Write("ran"); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, "var x = int.M"); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_TypeReceiver_02() + { + var src = """ +var x = int.M; + +public static class E +{ + extension(T t) + { + public void M() { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = int.M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "int.M").WithLocation(1, 9)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, "var x = int.M"); + Assert.True(model.GetTypeInfo(localDeclaration.Type).Type.IsErrorType()); + } + + [Fact] + public void FunctionType_TypeReceiver_03() + { + var src = """ +var x = int.M; + +public static class E +{ + public static void M(this T t) { } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = int.M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "int.M").WithLocation(1, 9)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, "var x = int.M"); + Assert.True(model.GetTypeInfo(localDeclaration.Type).Type.IsErrorType()); + } + + [Fact] + public void FunctionType_InstanceReceiver_01() + { + var src = """ +var x = 42.M; + +public static class E +{ + extension(T t) + { + public void M() { System.Console.Write(t); } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,9): error CS1113: Extension method 'E.extension(int).M()' defined on value type 'int' cannot be used to create delegates + // var x = 42.M; + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "42.M").WithArguments("E.extension(int).M()", "int").WithLocation(1, 9)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, "var x = 42.M"); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_InstanceReceiver_02() + { + var src = """ +var x = "ran".M; +x(); + +public static class E +{ + extension(T t) + { + public void M() { System.Console.Write(t); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, """var x = "ran".M"""); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_InstanceReceiver_03() + { + var src = """ +var x = 42.M; + +public static class E +{ + extension(T t) + { + public static void M() { } + } +} +"""; + 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_InstanceReceiver_04() + { + var src = """ +var x = 42.M; + +public static class E +{ + extension(int) + { + public void M() { } + } +} +"""; + 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_InstanceReceiver_05() + { + var src = """ +var x = "ran".M; +x(); + +public static class E +{ + extension(string s) + { + public void M() { System.Console.Write(s); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, """var x = "ran".M"""); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_InstanceReceiver_06() + { + var src = """ +var x = "ran".M; +x(); + +public static class E +{ + extension(string s) + { + public void M() { System.Console.Write(s); } + public void M(T t) { } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, """var x = "ran".M"""); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_InstanceReceiver_07() + { + var src = """ +namespace N +{ + public class C + { + public static void Main() + { + var x = "".M; + System.Console.Write(x); + } + } + + public static class E1 + { + extension(string s) + { + public void M() { } + } + } +} + +public static class E2 +{ + extension(string s) + { + public int M => 42; + } +} +"""; + var comp = CreateCompilation(src, options: TestOptions.DebugExe); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8917: The delegate type could not be inferred. + // var x = "".M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, @""""".M").WithLocation(7, 21)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, """var x = "".M"""); + Assert.True(model.GetTypeInfo(localDeclaration.Type).Type.IsErrorType()); + } + + [Fact] + public void FunctionType_InstanceReceiver_08() + { + var src = """ +namespace N +{ + public class C + { + public static void Main() + { + var x = "ran".M; + x(42); + } + } + + public static class E1 + { + extension(string s) + { + public void M() { } + } + } +} + +public static class E2 +{ + extension(string s) + { + public void M(int i) { System.Console.Write((s, i)); } + } +} +"""; + var comp = CreateCompilation(src, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "(ran, 42)").VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var localDeclaration = GetSyntax(tree, """var x = "ran".M"""); + Assert.Equal("System.Action", model.GetTypeInfo(localDeclaration.Type).Type.ToTestDisplayString()); + } + + [Fact] + public void FunctionType_InstanceReceiver_09() + { + var src = """ +var x = "ran".M; + +public static class E1 +{ + extension(string s) + { + public void M() { } + } +} + +public static class E2 +{ + extension(string s) + { + public void M(int i) { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = "ran".M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, @"""ran"".M").WithLocation(1, 9)); + } + + [Fact] + public void FunctionType_InstanceReceiver_10() + { + var src = """ +System.Delegate x = "ran".M; +x.DynamicInvoke(); + +id("ran".M)(); + +T id(T t) => t; + +public static class E +{ + extension(string s) + { + public void M() { System.Console.Write("ran "); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran ran").VerifyDiagnostics(); + } + + [Fact] + public void FunctionType_ColorColorReceiver_01() + { + var src = """ +Color.M2(new Color()); + +public class Color +{ + public static void M2(Color Color) + { + var x = Color.M; + x(); + } +} + +public static class E +{ + extension(T t) + { + public void M() { System.Console.Write("ran"); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void FunctionType_ColorColorReceiver_02() + { + var src = """ +Color.M2(null); + +public class Color +{ + public static void M2(Color Color) + { + var x = Color.M; + x(); + } +} + +public static class E +{ + extension(T) + { + public static void M() { System.Console.Write("ran"); } + } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void FunctionType_ColorColorReceiver_03() + { + var src = """ +Color.M2(new Color()); + +public class Color +{ + public static void M2(Color Color) + { + var x = Color.M; + x(); + } + public void M() { System.Console.Write("ran"); } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void FunctionType_ColorColorReceiver_04() + { + var src = """ +Color.M2(null); + +public class Color +{ + public static void M2(Color Color) + { + var x = Color.M; + x(); + } + public static void M() { System.Console.Write("ran"); } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void FunctionType_ColorColorReceiver_05() + { + var src = """ +public class Color +{ + public static void M2(Color Color) + { + var x = Color.M; + } +} + +public static class E1 +{ + extension(T) + { + public static void M() { } + } +} + +public static class E2 +{ + extension(T) + { + public void M() { } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,17): error CS0121: The call is ambiguous between the following methods or properties: 'E1.extension(T).M()' and 'E2.extension(T).M()' + // var x = Color.M; + Diagnostic(ErrorCode.ERR_AmbigCall, "Color.M").WithArguments("E1.extension(T).M()", "E2.extension(T).M()").WithLocation(5, 17)); + } + + [Fact] + public void Params_ExtensionScopes_01() + { + var source = """ +static class E1 +{ + extension(N.C c) + { + public void M(int[] x) => System.Console.Write(3); + } +} + +namespace N +{ + static class E2 + { + extension(C c) + { + public void M(params int[] x) => System.Console.Write(2); + } + } + + class C + { + public void M(params int[] x) => System.Console.Write(1); + public static void Main() + { + var d = new C().M; + System.Console.WriteLine(d.GetType()); + d(); + } + } +} +"""; + + var expectedOutput = """ +<>f__AnonymousDelegate0`1[System.Int32] +1 +"""; + + CompileAndVerify(source, symbolValidator: validateSymbols, expectedOutput: expectedOutput).VerifyDiagnostics(); + + static void validateSymbols(ModuleSymbol module) + { + var m = module.GlobalNamespace.GetMember("<>f__AnonymousDelegate0.Invoke"); + Assert.Equal("void <>f__AnonymousDelegate0.Invoke(params T1[] arg)", m.ToTestDisplayString()); + } + } + + [Fact] + public void Params_ExtensionScopes_02() + { + var source = """ +static class E1 +{ + extension(N.C c) + { + public void M(params int[] x) => System.Console.Write(3); + } +} + +namespace N +{ + static class E2 + { + extension(C c) + { + public void M(params int[] x) => System.Console.Write(2); + } + } + + class C + { + public void M(params int[] x) => System.Console.Write(1); + public static void Main() + { + var d = new C().M; + System.Console.WriteLine(d.GetType()); + d(); + } + } +} +"""; + + var expectedOutput = """ +<>f__AnonymousDelegate0`1[System.Int32] +1 +"""; + + CompileAndVerify(source, symbolValidator: validateSymbols, expectedOutput: expectedOutput).VerifyDiagnostics(); + + static void validateSymbols(ModuleSymbol module) + { + var m = module.GlobalNamespace.GetMember("<>f__AnonymousDelegate0.Invoke"); + Assert.Equal("void <>f__AnonymousDelegate0.Invoke(params T1[] arg)", m.ToTestDisplayString()); + } + } + [Fact] + public void Params_ExtensionScopes_07() + { + var source = """ +static class E1 +{ + extension(N.C c) + { + public void M(params int[] x) => System.Console.Write(1); + } +} + +namespace N +{ + static class E2 + { + extension(C) + { + public void M(int[] x) => System.Console.Write(2); + } + } + + class C + { + public static void Main() + { + var d = new C().M; + System.Console.WriteLine(d.GetType()); + d(default); + } + } +} +"""; + + var expectedOutput = """ +System.Action`1[System.Int32[]] +2 +"""; + + CompileAndVerify(source, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Fact] + public void Params_ExtensionScopes_08() + { + var source = """ +static class E1 +{ + extension(N.C c) + { + public void M(int[] x) => System.Console.Write(1); + } +} + +namespace N +{ + static class E2 + { + extension(C c) + { + public void M(params int[] x) => System.Console.Write(2); + } + } + + class C + { + public static void Main() + { + var d = new C().M; + System.Console.WriteLine(d.GetType()); + d(); + } + } +} +"""; + + var expectedOutput = """ +<>f__AnonymousDelegate0`1[System.Int32] +2 +"""; + + CreateCompilation(source).VerifyDiagnostics(); + CompileAndVerify(source, symbolValidator: validateSymbols, expectedOutput: expectedOutput).VerifyDiagnostics(); + + static void validateSymbols(ModuleSymbol module) + { + var m = module.GlobalNamespace.GetMember("<>f__AnonymousDelegate0.Invoke"); + Assert.Equal("void <>f__AnonymousDelegate0.Invoke(params T1[] arg)", m.ToTestDisplayString()); + } + } } From c73f9d2acce92a0ef969796db703344c872a7c1c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 24 Mar 2025 13:35:05 -0700 Subject: [PATCH 2/2] Address feedback --- .../Symbols/MemberSymbolExtensions.cs | 20 +++--------- .../Test/Emit3/Semantics/ExtensionTests.cs | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs index 47071f89fa4e8..775b8e8a39edd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs @@ -163,7 +163,7 @@ internal static TMember ConstructIncludingExtension(this TMember member if (member is MethodSymbol method) { // 1. construct with explicit type arguments if provided - MethodSymbol constructed; + MethodSymbol? constructed; if (!typeArguments.IsDefaultOrEmpty && method.GetMemberArityIncludingExtension() == typeArguments.Length) { constructed = method.ConstructIncludingExtension(typeArguments); @@ -194,18 +194,13 @@ internal static TMember ConstructIncludingExtension(this TMember member else { Debug.Assert(method.GetIsNewExtensionMember()); - constructed = (MethodSymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType)!; + constructed = (MethodSymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType); - if (checkFullyInferred && constructed.IsGenericMethod && typeArguments.IsDefaultOrEmpty) + if (checkFullyInferred && constructed?.IsGenericMethod == true && typeArguments.IsDefaultOrEmpty) { return null; } } - - if ((object)constructed == null) - { - return null; - } } return constructed; @@ -215,14 +210,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 constructedProperty = (PropertySymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType)!; - - if (constructedProperty is null) - { - return null; - } - - return constructedProperty; + return (PropertySymbol?)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType); } throw ExceptionUtilities.UnexpectedValue(member.Kind); diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 743d80ed07cdf..aaf7b037e685e 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -29411,6 +29411,37 @@ public static class E CompileAndVerify(comp, expectedOutput: "ran ran").VerifyDiagnostics(); } + [Fact] + public void FunctionType_InstanceReceiver_11() + { + var src = """ +using N; + +var x = "ran".M; + +public static class E1 +{ + extension(T t) where T : struct + { + public void M() { } + } +} + +namespace N +{ + public static class E2 + { + extension(T t) + { + public void M() { } + } + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyDiagnostics(); + } + [Fact] public void FunctionType_ColorColorReceiver_01() {