Skip to content
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

Avoid several breaking changes in overload resolution from inferred types of lambda expressions and method groups #56341

Merged
merged 23 commits into from
Sep 22, 2021

Conversation

cston
Copy link
Member

@cston cston commented Sep 11, 2021

Update "better function member" to prefer members where none of the conversions and none of the type arguments involved inferred types from lambda expressions or method groups.

Better function member

...
Given an argument list A with a set of argument expressions {E1, E2, ..., En} and two applicable function members Mp and Mq with parameter types {P1, P2, ..., Pn} and {Q1, Q2, ..., Qn}, Mp is defined to be a better function member than Mq if

  1. for each argument, the implicit conversion from Ex to Px is not a function_type_conversion, and
    • Mp is a non-generic method or Mp is a generic method with type parameters {X1, X2, ..., Xp} and for each type parameter Xi the type argument is inferred from an expression or from a type other than a function_type, and
    • for at least one argument, the implicit conversion from Ex to Qx is a function_type_conversion, or Mq is a generic method with type parameters {Y1, Y2, ..., Yq} and for at least one type parameter Yi the type argument is inferred from a function_type, or
  2. for each argument, the implicit conversion from Ex to Qx is not better than the implicit conversion from Ex to Px, and for at least one argument, the conversion from Ex to Px is better than the conversion from Ex to Qx.

Update "better conversion from expression" to prefer conversions that did not involve inferred types from lambda expressions or method groups.

Better conversion from expression

Given an implicit conversion C1 that converts from an expression E to a type T1, and an implicit conversion C2 that converts from an expression E to a type T2, C1 is a better conversion than C2 if:

  1. C1 is not a function_type_conversion and C2 is a function_type_conversion, or
  2. E is a non-constant interpolated_string_expression, C1 is an implicit_string_handler_conversion, T1 is an applicable_interpolated_string_handler_type, and C2 is not an implicit_string_handler_conversion, or
  3. E does not exactly match T2 and at least one of the following holds:

The changes resolve several breaking changes between C#9 and C#10.

Fixes #55691
Fixes #56167
Fixes #56319

Relates to test plan #52192

@cston cston marked this pull request as ready for review September 13, 2021 17:27
@cston cston requested a review from a team as a code owner September 13, 2021 17:27
@jcouv jcouv self-assigned this Sep 13, 2021
@cston cston added this to the 17.0 milestone Sep 13, 2021
@jcouv
Copy link
Member

jcouv commented Sep 13, 2021

Regarding the proposed spec change:

a conversion from the inferred type of an anonymous method or a method group

Can this be simplified to "a function type conversion"?

Similarly, we can use the "function type" label as a shorthand in the third group.
Merging the bullets to reflect top-level OR with 3 groups.


In reply to: 918633815


In reply to: 918633815


In reply to: 918633815

{
public readonly ImmutableArray<TypeWithAnnotations> InferredTypeArguments;
public readonly bool InferredFromFunctionType;
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider adding a comment here and in MemberResolutionResult
nit: Consider aligning names between those two structures (TypeArgumentsFromFunctionType here?) #Closed

@@ -276,7 +279,7 @@ private enum Dependency
// Early out: if the method has no formal parameters then we know that inference will fail.
if (formalParameterTypes.Length == 0)
{
return new MethodTypeInferenceResult(false, default(ImmutableArray<TypeWithAnnotations>));
return new MethodTypeInferenceResult(false, default, false);
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider naming arguments #Closed

@@ -2449,6 +2464,16 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy
}
}

switch ((conv1.Kind, conv2.Kind))
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposed spec changes covers "better function member" and is implemented by BetterFunctionMember above. But I don't a spec change for better conversion from expression. I think this change is sensible, but let's spec it too. #Resolved


[WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")]
[Fact]
public void OverloadResolution_01A()
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding one more variation on this: void M<T>(T, Func<object>) vs. void M<T>(Func<object>, T) (remains error) #Closed


[WorkItem(4674, "https://github.com/dotnet/csharplang/issues/4674")]
[Fact]
public void OverloadResolution_01D()
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a variant of this scenario with M<T, U>(Func<T>, U) versus M<T, U>(T, U)? I think it will be ambiguous, but may be worth discussing (should we keep a per-argument rule like before rather than adding an aggregate rule where any function type conversion means "worse"?) #Closed

comp = CreateCompilation(source);
comp.VerifyDiagnostics(expectedDiagnostics10AndLater);
CompileAndVerify(source, parseOptions: TestOptions.Regular10);
CompileAndVerify(source);
Copy link
Member

@jcouv jcouv Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's verify which overload got picked. Ditto in tests below that are no longer breaks #Closed

Copy link
Member

@jcouv jcouv left a 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 (iteration 9)

@jcouv
Copy link
Member

jcouv commented Sep 14, 2021

This PR fixes a few recently opened issues. I think #56167 is one of them

{
// (10,11): error CS0428: Cannot convert method group 'F' to non-delegate type 'Expression'. Did you intend to invoke the method?
// M(F);
Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "F").WithArguments("F", "System.Linq.Expressions.Expression").WithLocation(10, 11)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, overload resolution should choose M(object) rather than M(Expression).

operator+(A a, Func<int> f)
");

// Breaking change from C#9.
Copy link
Member

@jcouv jcouv Sep 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's update the breaking change doc along with this and review that we've documented other breaks we've accepted. Could be done in a separate PR, but feels like it would fit well along with this. #Closed

Copy link
Member

@jcouv jcouv left a 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 (iteration 12)


2. In C# 10, lambda expressions and method groups with inferred type are implicitly convertible to `System.MulticastDelegate`, and bases classes and interfaces of `System.MulticastDelegate` including `object`,
and lambda expressions are implicitly convertible to `System.Linq.Expressions.Expression` and `System.Linq.Expressions.LambdaExpression`.
Copy link
Member

@jcouv jcouv Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lambda expressions

I'd suggest saying "lambda expressions and method groups" here, and convert bullet 3 to be bullet 2c. #Resolved

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 21) with a suggestion to consolidate breaking change bullet 3 into breaking change bullet 2.

static void Main()
{
F(() => () => 1, 2); // C#9: ok; C#10: ambiguous
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

"Ok" doesn't really explain the previous behavior. What overload is chosen? #Closed

static void Main()
{
F(() => () => 1, 2); // C#9: F(Func<Func<object>>, int); C#10: ambiguous
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// C#9: F(Func<Func>, int); C#10: ambiguous

This behavior change feels unexpected to me, perhaps we should follow up on it. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 23)

@jnm2
Copy link
Contributor

jnm2 commented Oct 11, 2021

So many people have noticed the breaks in RC1 that I wonder if this change is worth a mention in dotnet/core#6570.

@cston
Copy link
Member Author

cston commented Oct 11, 2021

I wonder if this change is worth a mention in dotnet/core#6570.

Thanks @jnm2, I've added an entry there.

cston pushed a commit to cston/aspnetcore that referenced this pull request Nov 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment