-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Method group natural type: only use methods that can be fully substituted and pass constraint checks #69226
Conversation
dc31635
to
b2afd51
Compare
3d83756
to
4ed1916
Compare
@@ -1231,9 +1264,7 @@ partial class B : A | |||
yield return getData("internal static void F(this object x, ref object y) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different parameter ref kind | |||
yield return getData("internal static object F(this object x) => throw null;", "internal static ref object F(this object x) => throw null;", "this.F", "F"); // different return ref kind | |||
yield return getData("internal static ref object F(this object x) => throw null;", "internal static object F(this object x) => throw null;", "this.F", "F"); // different return ref kind | |||
yield return getData("internal static void F(this object x, object y) { }", "internal static void F<T>(this object x, T y) { }", "this.F", "F"); // different arity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F");
Instead of removing this entry, consider changing the expected result, by adding the following arguments: ..., null, "A.F", "System.Action<System.Object>");
#ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the added tests are unfortunately verbose, I find them much more readable
yield return getData("internal static void F(this object x, object y) { }", "internal static void F<T>(this object x, T y) { }", "this.F<int>", "F<int>", null, "B.F", "System.Action<System.Int32>"); // different arity | ||
yield return getData("internal static void F<T>(this object x) { }", "internal static void F(this object x) { }", "this.F", "F"); // different arity |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// (5,34): error CS0123: No overload for 'F' matches delegate 'Action' | ||
// System.Delegate d = this.F<int>; | ||
Diagnostic(ErrorCode.ERR_MethDelegateMismatch, "F<int>").WithArguments("F", "System.Action").WithLocation(5, 34) | ||
}); // different type parameter constraints |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of removing this entry, consider changing the expected result, by removing the diagnostic argument. #ByDesign
// (6,34): error CS0123: No overload for 'F' matches delegate 'Action' | ||
// System.Delegate d = this.F<int>; | ||
Diagnostic(ErrorCode.ERR_MethDelegateMismatch, "F<int>").WithArguments("F", "System.Action").WithLocation(6, 34) | ||
}); // different type parameter constraints |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider changing the expected result, by removing the diagnostic argument. #ByDesign
[Fact] | ||
public void GenericExtensionMethod_Constraint() | ||
{ | ||
// In C# 12, a method group that cannot be successfully substituted (ie. respecting constraints) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Fact] | ||
public void GenericInstanceMethod_Constraint() | ||
{ | ||
// In C# 12, a method that cannot be successfully substituted (ie. respecting constraints) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
return null; | ||
} | ||
foreach (var instanceMethod in node.Methods) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return null; | ||
} | ||
|
||
// For the purpose of construction we use original type parameters in place of type arguments that we couldn't infer from the first argument. | ||
ImmutableArray<TypeWithAnnotations> typeArgsForConstruct = typeArgs; | ||
if (typeArgs.Any(static t => !t.HasType)) | ||
wasFullyInferred = !typeArgs.Any(static t => !t.HasType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""; | ||
var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); | ||
CompileAndVerify(comp, expectedOutput: "System.Action"); | ||
comp.VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void M() | ||
{ | ||
System.Delegate d = F; | ||
System.Console.Write(d.GetDelegateTypeName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
"""; | ||
var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); | ||
comp.VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1838,15 +1984,56 @@ static class B | |||
internal static void F(this object x) { } | |||
} | |||
} | |||
"""; | |||
var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: useCSharp13 ? TestOptions.RegularNext : TestOptions.RegularPreview); | |||
comp.VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// var z = new C().M; | ||
Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(4, 9) | ||
); | ||
comp.VerifyDiagnostics(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// var x = new object().F; | ||
Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new object().F").WithLocation(1, 9)); | ||
|
||
CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this is applicable to other tests as well.
[Fact] | ||
public void GenericExtensionMethod_ArityCountsInSignature() | ||
{ | ||
// Arity counts as part of "signature" so we have two different signatures even after reducing extension methods |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that makes sense
internal static void F(this object x) { } | ||
} | ||
"""; | ||
CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (commit 1) |
@@ -338,6 +338,7 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol> | |||
considerTypeConstraints: false, | |||
refKindCompareMode: RefKindCompareMode.ConsiderDifferences, | |||
considerCallingConvention: false, | |||
considerArity: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 although the legacy logic uses this comparer, it is not affected by this change since it only performs substitution/reduction once a unique signature is found. #Closed
@@ -338,6 +338,7 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol> | |||
considerTypeConstraints: false, | |||
refKindCompareMode: RefKindCompareMode.ConsiderDifferences, | |||
considerCallingConvention: false, | |||
considerArity: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although the legacy logic uses this comparer, it is not affected by this change since it only performs substitution/reduction once a unique signature is found.
Let's suppose we have two generic methods with different arity, but with the same signature. For example, none of them refer to their type parameters in the signature. Isn't this change going to affect their comparison before substitution?
It feels to me, that keeping a separate comparer for the legacy logic is a small enough price to pay for a peace of mind that the logic isn't broken. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, the difference is visible in that scenario. Added a test and a legacy comparer. Thanks!
Done with review pass (commit 4) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 5)
Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md
There's three parts to this change:
Relates to test plan #69432