Repository: dotnet/roslyn
Pull request #77532: Extensions: Handle explicit type arguments
Author: login: jcouv
Labels: name: Area-Compilers
name: Feature - Extension Everything
description: The extension everything feature
name: untriaged
description: Issues and PRs which have not yet been triaged by a lead
Created: 2025-03-11T18:35:53Z | Merged: 2025-03-12T23:21:52Z

As part of resolving new extension members like classic extension methods, LDM decided that the explicit type arguments would apply to the extension declaration and member together (with a combined type parameter list). 
The spec was updated already: https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md#consumption

Relates to test plan https://github.com/dotnet/roslyn/issues/76130


Diff:
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
index fe54519a8d1d6..fc602212e1978 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
@@ -8511,7 +8511,6 @@ protected MethodGroupResolution ResolveExtension(
             AnalyzedArguments? actualMethodArguments = null;
             AnalyzedArguments? actualReceiverArguments = null;
 
-            // PROTOTYPE we need to lookup on combined arity of extension declaration and extension method
             int arity = typeArgumentsWithAnnotations.IsDefault ? 0 : typeArgumentsWithAnnotations.Length;
             var lookupOptions = (arity == 0) ? LookupOptions.AllMethodsOnArityZero : LookupOptions.Default;
             if (analyzedArguments is not null)
@@ -8610,7 +8609,8 @@ static bool tryResolveExtensionInScope(
                     lookupResult, analyzedArguments, ref actualMethodArguments, options, in callingConvention, binder, diagnostics);
 
                 // 3. resolve properties
-                OverloadResolutionResult<PropertySymbol>? propertyResult = resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo);
+                Debug.Assert(arity == 0 || lookupResult.Symbols.All(s => s.Kind != SymbolKind.Property));
+                OverloadResolutionResult<PropertySymbol>? propertyResult = arity != 0 ? null : resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo);
 
                 // 4. determine member kind
                 if (!methodResult.HasAnyApplicableMethod && propertyResult?.HasAnyApplicableMember != true)
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
index 09e28f6940590..4647f2d0c8241 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
@@ -1839,7 +1839,7 @@ private static bool WrongArity(Symbol symbol, int arity, bool diagnose, LookupOp
                     if (arity != 0 || (options & LookupOptions.AllMethodsOnArityZero) == 0)
                     {
                         MethodSymbol method = (MethodSymbol)symbol;
-                        if (method.Arity != arity)
+                        if (method.GetMemberTotalArity() != arity)
                         {
                             if (method.Arity == 0)
                             {
diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
index 371ad29d95bd6..3bd6c9a1511af 100644
--- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
@@ -791,7 +791,7 @@ static MemberResolutionResult<TMember> makeWrongCallingConvention(MemberResoluti
 
         private bool FailsConstraintChecks<TMember>(TMember member, out ArrayBuilder<TypeParameterDiagnosticInfo> constraintFailureDiagnosticsOpt, CompoundUseSiteInfo<AssemblySymbol> template) where TMember : Symbol
         {
-            int arity = GetMemberTotalArity(member);
+            int arity = member.GetMemberTotalArity();
             if (arity == 0 || member.OriginalDefinition == (object)member)
             {
                 constraintFailureDiagnosticsOpt = null;
@@ -1169,7 +1169,7 @@ private void AddMemberToCandidateSet<TMember>(
             // This is specifying an impossible condition; the member lookup algorithm has already filtered
             // out methods from the method group that have the wrong generic arity.
 
-            Debug.Assert(typeArguments.Count == 0 || typeArguments.Count == member.GetMemberArity()); // PROTOTYPE deal with explicit type arguments
+            Debug.Assert(typeArguments.Count == 0 || typeArguments.Count == member.GetMemberTotalArity());
 
             // Second, we need to determine if the method is applicable in its normal form or its expanded form.
             bool disallowExpandedNonArrayParams = (options & Options.DisallowExpandedNonArrayParams) != 0;
@@ -2405,14 +2405,14 @@ private BetterResult BetterFunctionMember<TMember>(
             }
 
             // If MP is a non-generic method and MQ is a generic method, then MP is better than MQ.
-            if (GetMemberTotalArity(m1.Member) == 0)
+            if (m1.Member.GetMemberTotalArity() == 0)
             {
-                if (GetMemberTotalArity(m2.Member) > 0)
+                if (m2.Member.GetMemberTotalArity() > 0)
                 {
                     return BetterResult.Left;
                 }
             }
-            else if (GetMemberTotalArity(m2.Member) == 0)
+            else if (m2.Member.GetMemberTotalArity() == 0)
             {
                 return BetterResult.Right;
             }
@@ -2606,16 +2606,6 @@ static TypeSymbol getParameterTypeAndRefKind(int i, MemberAnalysisResult memberR
             }
         }
 
-        private static int GetMemberTotalArity<TMember>(TMember member) where TMember : Symbol
-        {
-            if (member.GetIsNewExtensionMember())
-            {
-                return member.ContainingType.Arity + member.GetMemberArity();
-            }
-
-            return member.GetMemberArity();
-        }
-
         /// <summary>
         /// Returns true if the overload required a function type conversion to infer
         /// generic method type arguments or to convert to parameter types.
@@ -4259,7 +4249,7 @@ private MemberResolutionResult<TMember> IsApplicable<TMember>(
             EffectiveParameters constructedEffectiveParameters;
             bool hasTypeArgumentsInferredFromFunctionType = false;
             if ((options & Options.InferringUniqueMethodGroupSignature) == 0 &&
-                GetMemberTotalArity(member) > 0)
+                member.GetMemberTotalArity() > 0)
             {
                 ImmutableArray<TypeWithAnnotations> typeArguments;
                 bool isNewExtensionMember = member.GetIsNewExtensionMember();
diff --git a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs
index f4c8291bba1b8..2f5be38c41bff 100644
--- a/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs
@@ -99,6 +99,16 @@ internal static bool GetIsNewExtensionMember(this PropertySymbol member)
         {
             return member.ContainingSymbol is TypeSymbol { IsExtension: true };
         }
+
+        internal static int GetMemberTotalArity(this Symbol member)
+        {
+            if (member.GetIsNewExtensionMember())
+            {
+                return member.ContainingType.Arity + member.GetMemberArity();
+            }
+
+            return member.GetMemberArity();
+        }
 #nullable disable
 
         /// <summary>
diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
index 7c427d235310a..02d4f47e612f9 100644
--- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
+++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
@@ -10653,8 +10653,8 @@ static class E
         Assert.Equal("void E.<>E__0<System.Int32>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
     }
 
-    [Fact(Skip = "PROTOTYPE explicit type arguments")]
-    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember()
+    [Fact]
+    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_01()
     {
         var src = """
 new C<int>().M<string>();
@@ -10670,18 +10670,45 @@ static class E
 }
 """;
         var comp = CreateCompilation(src);
-        comp.VerifyEmitDiagnostics();
-        // PROTOTYPE metadata is undone
-        //CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+        comp.VerifyEmitDiagnostics(
+            // (1,14): error CS1061: 'C<int>' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'C<int>' could be found (are you missing a using directive or an assembly reference?)
+            // new C<int>().M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("C<int>", "M").WithLocation(1, 14));
 
         var tree = comp.SyntaxTrees.First();
         var model = comp.GetSemanticModel(tree);
         var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<string>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_02()
+    {
+        var src = """
+new C<int>().M<int, string>();
+
+class C<T> { }
+
+static class E
+{
+    extension<T>(C<T> c)
+    {
+        public void M<U>() { System.Console.Write("ran"); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics();
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<int, string>()");
         Assert.Equal("void E.<>E__0<System.Int32>.M<System.String>()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
     }
 
-    [Fact(Skip = "PROTOTYPE explicit type arguments")]
-    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_OmittedTypeArgument()
+    [Fact]
+    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_OmittedTypeArgument_01()
     {
         var src = """
 new C<int>().M<,>();
@@ -10700,19 +10727,53 @@ static class E
         comp.VerifyEmitDiagnostics(
             // (1,1): error CS8389: Omitting the type argument is not allowed in the current context
             // new C<int>().M<,>();
-            Diagnostic(ErrorCode.ERR_OmittedTypeArgument, "new C<int>().M<,>").WithLocation(1, 1));
+            Diagnostic(ErrorCode.ERR_OmittedTypeArgument, "new C<int>().M<,>").WithLocation(1, 1),
+            // (1,14): error CS1061: 'C<int>' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'C<int>' could be found (are you missing a using directive or an assembly reference?)
+            // new C<int>().M<,>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<,>").WithArguments("C<int>", "M").WithLocation(1, 14));
 
         var tree = comp.SyntaxTrees.First();
         var model = comp.GetSemanticModel(tree);
         var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<,>()");
-        Assert.Equal("void E.<>E__0<System.Int32>.M<?, ?>()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
     }
 
-    [Fact(Skip = "PROTOTYPE explicit type arguments")]
+    [Fact]
+    public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_OmittedTypeArgument_02()
+    {
+        var src = """
+new C<int>().M<,,>();
+
+class C<T> { }
+
+static class E
+{
+    extension<T>(C<T> c)
+    {
+        public void M<U, V>() => throw null;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,1): error CS8389: Omitting the type argument is not allowed in the current context
+            // new C<int>().M<,,>();
+            Diagnostic(ErrorCode.ERR_OmittedTypeArgument, "new C<int>().M<,,>").WithLocation(1, 1),
+            // (1,1): error CS1929: 'C<int>' does not contain a definition for 'M' and the best extension method overload 'E.extension<?>(C<?>).M<?, ?>()' requires a receiver of type 'C<?>'
+            // new C<int>().M<,,>();
+            Diagnostic(ErrorCode.ERR_BadInstanceArgType, "new C<int>()").WithArguments("C<int>", "M", "E.extension<?>(C<?>).M<?, ?>()", "C<?>").WithLocation(1, 1));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<,,>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
     public void InstanceMethodInvocation_MatchingExtendedType_GenericType_GenericMember_BrokenConstraint()
     {
         var src = """
-new C<int>().M<string>();
+new C<int>().M<int, string>();
 
 class C<T> { }
 
@@ -10727,12 +10788,12 @@ static class E
         var comp = CreateCompilation(src);
         comp.VerifyEmitDiagnostics(
             // (1,14): error CS0453: The type 'string' must be a non-nullable value type in order to use it as parameter 'U' in the generic type or method 'E.extension<int>(C<int>).M<U>()'
-            // new C<int>().M<string>();
-            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<string>").WithArguments("E.extension<int>(C<int>).M<U>()", "U", "string").WithLocation(1, 14));
+            // new C<int>().M<int, string>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<int, string>").WithArguments("E.extension<int>(C<int>).M<U>()", "U", "string").WithLocation(1, 14));
 
         var tree = comp.SyntaxTrees.First();
         var model = comp.GetSemanticModel(tree);
-        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<string>()");
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "new C<int>().M<int, string>()");
         Assert.Null(model.GetSymbolInfo(invocation).Symbol);
     }
 
@@ -12444,11 +12505,11 @@ static class E1
         //Assert.Empty(model.GetMemberGroup(memberAccess)); // PROTOTYPE semantic model is undone
     }
 
-    [Fact(Skip = "PROTOTYPE explicit type arguments")]
+    [Fact]
     public void InstanceMethodInvocation_MultipleSubstitutions()
     {
         var src = """
-new C().M<int>();
+new C().M();
 
 interface I<T> { }
 class C : I<int>, I<string> { }
@@ -12457,19 +12518,19 @@ static class E
 {
     extension<T>(I<T> i)
     {
-        public void M<U>() { }
+        public void M() { }
     }
 }
 """;
         var comp = CreateCompilation(src);
         comp.VerifyEmitDiagnostics(
             // (1,9): error CS1061: 'C' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
-            // new C().M<int>();
-            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<int>").WithArguments("C", "M").WithLocation(1, 9));
+            // new C().M();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M").WithArguments("C", "M").WithLocation(1, 9));
 
         var tree = comp.SyntaxTrees.Single();
         var model = comp.GetSemanticModel(tree);
-        var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "new C().M<int>");
+        var memberAccess = GetSyntax<MemberAccessExpressionSyntax>(tree, "new C().M");
         Assert.Null(model.GetSymbolInfo(memberAccess).Symbol);
         Assert.Equal([], model.GetSymbolInfo(memberAccess).CandidateSymbols.ToTestDisplayStrings());
         Assert.Empty(model.GetMemberGroup(memberAccess));
@@ -23520,4 +23581,186 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write(s); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_05()
+    {
+        var src = """
+string s = null;
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M<U>() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,3): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // s.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("string", "M").WithLocation(2, 3));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_06()
+    {
+        var src = """
+C<string, string>.M<string>();
+
+class C<T1, T2> { }
+static class E
+{
+    extension<T, U>(C<T, U> t)
+    {
+        public static void M() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,19): error CS0117: 'C<string, string>' does not contain a definition for 'M'
+            // C<string, string>.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMember, "M<string>").WithArguments("C<string, string>", "M").WithLocation(1, 19));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_07()
+    {
+        var src = """
+string.M<object, long>(42);
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public static void M<U>(U u) { System.Console.Write("ran"); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "string.M<object, long>(42)");
+        Assert.Equal("void E.<>E__0<System.Object>.M<System.Int64>(System.Int64 u)", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_08()
+    {
+        var src = """
+object.M<string, long>(42);
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public static void M<U>(U u) { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension<string>(string).M<long>(long)' requires a receiver of type 'string'
+            // object.M<string, long>(42);
+            Diagnostic(ErrorCode.ERR_BadInstanceArgType, "object").WithArguments("object", "M", "E.extension<string>(string).M<long>(long)", "string").WithLocation(1, 1));
+    }
 }


Reviews:
user: login: jjonescz
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-12T09:16:57Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:02:35Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:05:50Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:46:24Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:47:49Z
user: login: jcouv
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T19:20:26Z
user: login: jcouv
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T19:23:01Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T19:53:11Z
user: login: jcouv
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T20:18:50Z
user: login: AlekseyTs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:55:08Z
user: login: AlekseyTs
body: LGTM (commit 2)
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:57:03Z

Review comments:
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_05()
+    {
+        var src = """
+string s = null;
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M<U>() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,3): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // s.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("string", "M").WithLocation(2, 3));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_06()
+    {
+        var src = """
+C<string, string>.M<string>();
+
+class C<T1, T2> { }
+static class E
+{
+    extension<T, U>(C<T, U> t)
+    {
+        public void M() { }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: jjonescz
body: Consider testing a similar case but where `M` is `static`. #Resolved
created_at: 2025-03-12T09:15:07Z
author_association: MEMBER
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: jjonescz
body: 
```suggestion
        public void M<T>() { System.Console.Write(s); }
``` #Resolved
created_at: 2025-03-12T09:16:26Z
author_association: MEMBER
diff_hunk: @@ -196,7 +196,14 @@ private void LookupExtensionMembersInSingleBinder(LookupResult result, TypeSymbo
             {
                 // No need for "diagnose" since we discard the lookup results (and associated diagnostic info)
                 // unless the results are good.
-                LookupMembersInClass(tempResult, extension, name, arity, basesBeingResolved,
+                int remainingArity = arity == 0 ? 0 : arity - extension.Arity;
path: src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
user: login: AlekseyTs
body: >int remainingArity = arity == 0 ? 0 : arity - extension.Arity; [](http://example.com/codeflow?start=16&length=62)

It feels like we might pick up properties when ```remainingArity``` is 0, but  ```arity``` is not 0. I think we should prevent that, i.e. look up should not return properties in a situation like that. Therefore, I think we should adjust behavior of ```WrongArity``` instead. #Closed
created_at: 2025-03-12T17:02:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8610,7 +8610,7 @@ static bool tryResolveExtensionInScope(
                     lookupResult, analyzedArguments, ref actualMethodArguments, options, in callingConvention, binder, diagnostics);
 
                 // 3. resolve properties
-                OverloadResolutionResult<PropertySymbol>? propertyResult = resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo);
+                OverloadResolutionResult<PropertySymbol>? propertyResult = arity != 0 ? null : resolveProperties(left, lookupResult, binder, ref actualReceiverArguments, ref useSiteInfo);
path: src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
user: login: AlekseyTs
body: >arity != 0 ? null :  [](http://example.com/codeflow?start=75&length=20)

Given a comment in ```LookupExtensionMembersInSingleBinder```, I think this logic should not be needed for correctness, there should be no properties in the lookup result and this code is merely an optimization. Consider asserting that. #Closed
created_at: 2025-03-12T17:05:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: AlekseyTs
body: >M<T> [](http://example.com/codeflow?start=20&length=4)

Why is this candidate considered better? #Closed
created_at: 2025-03-12T18:46:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_05()
+    {
+        var src = """
+string s = null;
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M<U>() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,3): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // s.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("string", "M").WithLocation(2, 3));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_06()
+    {
+        var src = """
+C<string, string>.M<string>();
+
+class C<T1, T2> { }
+static class E
+{
+    extension<T, U>(C<T, U> t)
+    {
+        public void M() { }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: AlekseyTs
body: Second that. I think we need to add coverage for static members as well
created_at: 2025-03-12T18:47:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: jcouv
body: `string` is a more specific parameter type than `T`.

```
string s = "ran";
s.M<string>();

static class E
{
    public static void M<T>(this T t)
    {
    }
    public static void M<T>(this string s)
    {
        System.Console.Write(s);
    }
}
```
[sharplab](https://sharplab.io/#v2:C4LgTgrgdgPgAgRgAwAIDOKC8KBEYCGUOA3ALABQaAdALIA8iSAfABQCUZ5FiAbCnACYUAUQoBvCiin8AzPwR84AFhT0AKq2AALAJYY1KYG0nSJ5aSgC+JqXDm9+K9Zt0ZG6Y+dM2L8qgGEAeyg0QIAbAFMqAHUwHWAIljQOH2tySyA=)

created_at: 2025-03-12T19:20:26Z
author_association: MEMBER
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_05()
+    {
+        var src = """
+string s = null;
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M<U>() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,3): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // s.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("string", "M").WithLocation(2, 3));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_06()
+    {
+        var src = """
+C<string, string>.M<string>();
+
+class C<T1, T2> { }
+static class E
+{
+    extension<T, U>(C<T, U> t)
+    {
+        public void M() { }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: jcouv
body: Thanks. That was a typo (intended for `static` here)
created_at: 2025-03-12T19:23:01Z
author_association: MEMBER
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: AlekseyTs
body: But there is no `T` there, it is substituted with string. Is it some sort of a genericity check?
created_at: 2025-03-12T19:53:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write("ran"); }
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: jcouv
body: This rule applies to unsubstituted parameters:
``
            // Otherwise, if MP has more specific parameter types than MQ, then MP is better than
            // MQ. Let {R1, R2, …, RN} and {S1, S2, …, SN} represent the uninstantiated and
            // unexpanded parameter types of MP and MQ. MP's parameter types are more specific than
            // MQ's if, for each parameter, RX is not less specific than SX, and, for at least one
            // parameter, RX is more specific than SX

            // NB: OriginalDefinition, not ConstructedFrom.  Substitutions into containing symbols
            // must also be ignored for this tie-breaker.
```
created_at: 2025-03-12T20:18:50Z
author_association: MEMBER
diff_hunk: @@ -23520,4 +23581,142 @@ .param [1]
             // _ = i.P(1);
             Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P").WithArguments("int", "P").WithLocation(2, 7));
     }
+    [Fact]
+    public void ExplicitTypeArguments_01()
+    {
+        var src = """
+string s = "ran";
+s.M<object>();
+
+static class E
+{
+    extension<T>(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 invocation = GetSyntax<InvocationExpressionSyntax>(tree, "s.M<object>()");
+        Assert.Equal("void E.<>E__0<System.Object>.M()", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString());
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_02()
+    {
+        var src = """
+42.M<object>();
+
+static class E
+{
+    extension<T>(T t) where T : struct
+    {
+        public void M() { System.Console.Write(t); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (1,4): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.extension<T>(T)'
+            // 42.M<object>();
+            Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "M<object>").WithArguments("E.extension<T>(T)", "T", "object").WithLocation(1, 4));
+
+        var tree = comp.SyntaxTrees.First();
+        var model = comp.GetSemanticModel(tree);
+        var invocation = GetSyntax<InvocationExpressionSyntax>(tree, "42.M<object>()");
+        Assert.Null(model.GetSymbolInfo(invocation).Symbol);
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_03()
+    {
+        var src = """
+string s = "ran";
+_ = s.P<object>;
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public int P => 0;
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,7): error CS1061: 'string' does not contain a definition for 'P' and no accessible extension method 'P' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // _ = s.P<object>;
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "P<object>").WithArguments("string", "P").WithLocation(2, 7));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_04()
+    {
+        var src = """
+string s = "ran";
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M() => throw null;
+    }
+    extension(string s)
+    {
+        public void M<T>() { System.Console.Write(s); }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_05()
+    {
+        var src = """
+string s = null;
+s.M<string>();
+
+static class E
+{
+    extension<T>(T t)
+    {
+        public void M<U>() { }
+    }
+}
+""";
+        var comp = CreateCompilation(src);
+        comp.VerifyEmitDiagnostics(
+            // (2,3): error CS1061: 'string' does not contain a definition for 'M' and no accessible extension method 'M' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
+            // s.M<string>();
+            Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M<string>").WithArguments("string", "M").WithLocation(2, 3));
+    }
+
+    [Fact]
+    public void ExplicitTypeArguments_06()
+    {
+        var src = """
+C<string, string>.M<string>();
+
path: src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
user: login: AlekseyTs
body: Do we have a test for a success scenario for static extension with explicit type arguments? #Resolved
created_at: 2025-03-12T20:55:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: jcouv
created_at: 2025-03-11T22:53:31Z
body: @AlekseyTs @jjonescz for review. Thanks
author_association: MEMBER
user: login: AlekseyTs
created_at: 2025-03-12T16:50:48Z
body: >             // PROTOTYPE we need to lookup on combined arity of extension declaration and extension method

Is this comment still relevant? #Closed

---
Refers to: src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs:8514 in 012c385. [](commit_id = 012c38566a437c790e56066a332eb938f861367b, deletion_comment = False)
author_association: CONTRIBUTOR
user: login: AlekseyTs
created_at: 2025-03-12T18:48:35Z
body: Done with review pass (commit 1)
author_association: CONTRIBUTOR