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

Ignore function types in return type inference #57713

Merged
merged 5 commits into from
Nov 18, 2021

Conversation

cston
Copy link
Member

@cston cston commented Nov 11, 2021

With C#10 currently, there is a potential breaking change from C#9 in type inference when inferring from the return type of a lambda expression when the return value is another lambda expression or a method group.

See examples in #57517 (comment) and #57630.

To avoid the breaking change, type inference is updated to not infer from the return type of a lambda if the return value is another lambda or method group. This effectively reverts the type inference behavior for those cases to the C#9 behavior.

Fixes #57630.

See also https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1437668.

var comp = CreateCompilation(source);
comp.VerifyDiagnostics(

var expectedDiagnostics = new[]
Copy link
Member Author

@cston cston Nov 12, 2021

Choose a reason for hiding this comment

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

var expectedDiagnostics = new[]

There are no changes to the behavior of OverloadResolution_17, _18, _19, _20. The tests have simply been expanded to test -langversion:9 and -langversion:10 as well.

@cston cston marked this pull request as ready for review November 12, 2021 18:50
@cston cston requested a review from a team as a code owner November 12, 2021 18:50
@cston cston changed the base branch from main to release/dev17.0-vs-deps November 12, 2021 20:34
@cston cston requested review from a team as code owners November 12, 2021 20:34
@cston
Copy link
Member Author

cston commented Nov 13, 2021

@dotnet/roslyn-compiler, please review this fix for 17.0, thanks.

@cston cston added this to the 17.0 milestone Nov 13, 2021
Diagnostic(ErrorCode.WRN_UnreferencedLocalFunction, "Test4").WithArguments("Test4").WithLocation(7, 6),
// (11,1): error CS0411: The type arguments for method 'Test3<T>(Func<Func<T>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// Test3(() => () => 3);
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Test3").WithArguments("Test3<T>(System.Func<System.Func<T>>)").WithLocation(11, 1),

Choose a reason for hiding this comment

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

I'm not familiar at all with the rules/specifications around this so feel free to ignore, but is it expected that this case shouldn't support inference? In my bug report (57517) I mentioned that Func<Func<T>> supported inference of T and it seems this change is going in the opposite direction and undoing something that was working? When I made the bug report I was thinking that the outcome would be that inference of T on Func<Expression<Func<T>>> would start working.

Copy link
Member Author

@cston cston Nov 14, 2021

Choose a reason for hiding this comment

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

Thanks @CameronAavik for catching this and for logging #57517.

With C#10 currently, there is a potential breaking change from C#9 in type inference when inferring from the return type of a lambda expression when the return value is another lambda expression or a method group
(see examples in #57517 (comment) and #57630).

We've decided to fix the breaking change for now by not inferring from the return type of a lambda if the return value is another lambda or method group. This effectively reverts the type inference behavior for those cases to the C#9 behavior.

(I've added this to the PR description above.)

In your examples above, as you noted, this means that inference fails for the calls to Test3() and Test4(), as in C#9.

Separately, we will investigate whether we can improve type inference (in a subsequent release) for these cases by inferring from the function type of the returned lambda or method group. If so, that should allow inference for Test3() and Test4().

I will log a separate issue for that investigation.

@cston
Copy link
Member Author

cston commented Nov 15, 2021

@dotnet/roslyn-compiler, please review, thanks.

@@ -85,18 +88,18 @@ public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, BoundBlock bo
);
}

public TypeWithAnnotations GetInferredReturnType(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
public TypeWithAnnotations GetInferredReturnType(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool allowInferredFromFunctionType)
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 15, 2021

Choose a reason for hiding this comment

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

allowInferredFromFunctionType

The name sounds awkward. Would "allowInferenceFromFunctionType" be better? #Closed


TypeWithAnnotations inferredType;
if (bestType is { })
{
// Note: so long as we have a best type, we can proceed.
var bestTypeWithObliviousAnnotation = TypeWithAnnotations.Create(bestType);
var unwrappedType = bestType is FunctionTypeSymbol functionType ?
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 15, 2021

Choose a reason for hiding this comment

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

var unwrappedType = bestType is FunctionTypeSymbol functionType ?

Would it be better to pass unwrapFunctionType: true above? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Based on your suggestion offline, I've changed the various methods to return a TypeSymbol and a bool rather than requiring callers to pass in the correct value.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Nov 15, 2021

            inferredType = TypeWithAnnotations.Create(bestType, BestTypeInferrer.GetNullableAnnotation(resultTypes));

It looks like we can end up with FunctionTypeSymbol here. Is this intentional? #Closed


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:3569 in 692a53b. [](commit_id = 692a53b, deletion_comment = False)


if (!allowInferredFromFunctionType && inferredReturnType.InferredFromFunctionType)
{
return default;
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 15, 2021

Choose a reason for hiding this comment

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

return default;

I would expect a much simpler change where a function type wouldn't be simply added to the set of candidates. That would revert behavior to C# 9 behavior, I think. #Closed

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 2)

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 4)

bestResultType = default;
break;
case 1:
if (conversions.IncludeNullability)
{
inferredFromFunctionType = false;
Copy link
Member

Choose a reason for hiding this comment

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

I didn't follow, why is inferredFromFunctionType always false here?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's assumed that the caller has populated the returns array with the actual types of the corresponding BoundExpression (although if the caller is NullableWalker, the types may differ by inferred nullability). That assumption is unchanged with this PR since callers did not expect this method to return a function type previously.

@jcouv
Copy link
Member

jcouv commented Nov 15, 2021

Do we have a follow-up issue to either make further adjustments for 17.1, or to reify the current behavior into the spec (if we choose to stick with this behavior)?

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 4)

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 4)

@cston
Copy link
Member Author

cston commented Nov 15, 2021

Logged #57779 to investigate inferring from the function type instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants