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

Allow extension methods on tuple receivers when underlying conversions are "upcasts" #16575

Merged
merged 1 commit into from
Jan 18, 2017

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Jan 18, 2017

Allow extension methods on tuple receivers when underlying conversions are "upcasts"

Fixes:#16159

Extensions methods declared on bases and implemented interfaces are allowed to be applied to receivers that of derived or implementing type.

This change extends this rule to tuple receivers as long as the rule holds for individual elements, recursively.

I.E. valid "this" conversions are:

  • identity conversion
  • implicit reference conversion
  • boxing conversion
  • (new) implicit tuple[literal] conversions as long as all underlying conversions are valid "this" conversions.

Customer scenario

Customer defines an extension method on (Base, Base) and expects it to be applicable to expressions of type (Derived, Derived)
To his surprise, code does not compile or compiles, but picks a different less specific overload.

Bugs this fixes:

#16159

Workarounds, if any

Customer can cast to the exact type on which extension was declared.
This could be inconvenient, especially in cases where extension is defined in terms of very abstract types like the proposed Zip linq operator that takes (IEnumerable<T>, IEnumerable<U>)
The usefulness of an abstract implementation diminishes if user needs to cast when calling it with a concrete receiver types (which is most of the time)

Risk

The fix is very specific to the kind of tuple conversions that are permitted on a receiver when validating an extension method candidate

Performance impact

"Low"

Is this a regression from a previous update?

New feature.

Root cause analysis:

We did not consider this scenario.

How was the bug found?

customer reported

…s are "upcasts"

Extensions methods declared on bases and implemented interfaces are allowed to be applied to receivers that of derived or implementing type.

This change extends this rule to tuple receivers as long as the rule holds for individual elements, recursively.

I.E. valid "this" conversions are:
- identity conversion
- implicit reference conversion
- boxing conversion
- (new)  implicit tuple[literal] conversions as long as all underlying conversions are valid "this" conversions.
@@ -5360,7 +5360,12 @@ private void BindMemberAccessReportError(BoundMethodGroup node, DiagnosticBag di
}
else
{
if (boundLeft.Kind == BoundKind.TypeExpression ||

if ((object)boundLeft.Type == null)
Copy link
Member Author

Choose a reason for hiding this comment

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

We had a bug here, that could be hit even without this change.
Typeless expressions could cause crashes or serialization asserts during error reporting.

Copy link
Member

Choose a reason for hiding this comment

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

Is there a test in this PR that would have crashed before?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, ExtensionMethodOnConvertedTuple002Err would crash

@VSadov
Copy link
Member Author

VSadov commented Jan 18, 2017

@dotnet/roslyn-compiler - please review

@jcouv jcouv added this to the 2.0 (RTM) milestone Jan 18, 2017
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitTupleLiteral:
// check if all element conversions satisfy the requirement
foreach (var elementConversion in conversion.UnderlyingConversions)
Copy link
Member

Choose a reason for hiding this comment

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

I can never remember which ImmutableArray extensions we have and which we are still missing, but if we have it, consider conversion.UnderlyingConversions.All(c => IsValidExtensionMethodThisArgConversion(c)).

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.

Nice, thanks

@@ -5360,7 +5360,12 @@ private void BindMemberAccessReportError(BoundMethodGroup node, DiagnosticBag di
}
else
{
if (boundLeft.Kind == BoundKind.TypeExpression ||

if ((object)boundLeft.Type == null)
Copy link
Member

Choose a reason for hiding this comment

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

Is there a test in this PR that would have crashed before?


var comp = CreateCompilationWithMscorlib(source, references: s_valueTupleRefs.Concat(new[] { LinqAssemblyRef }));
comp.VerifyDiagnostics(
// (10,41): error CS1928: '(int, int)' does not contain a definition for 'M' and the best extension method overload 'C.M((int x, long y))' has some invalid arguments
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this diagnostic. What argument is invalid?

Copy link
Member Author

@VSadov VSadov Jan 18, 2017

Choose a reason for hiding this comment

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

"this" is of type (int int), but expected to be (int, long).
While that has implicit conversion, it is not in the set of the allowed upcast-like conversions, int->long is not a cast to base or to an implemented interface, so it fails.

The error could be more detailed, but it is a preexisting error, that we are not fixing here.

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

LGTM

@VSadov
Copy link
Member Author

VSadov commented Jan 18, 2017

@MattGertz - need RTW approval. Thanks!

@VSadov
Copy link
Member Author

VSadov commented Jan 18, 2017

@MattGertz
Copy link
Contributor

Side chat with Neal:

"Yes, it would be a breaking change to fix it later. That’s because it changes what methods are candidates in overload resolution. The method selected in overload resolution after this change may be different from the method selected in overload resolution without the change. So making the change later could silently change the meaning of existing code. That’s a kind of breaking change that we try very hard to avoid."

Customer-reported omission, and it would be a breaking change to fix later, so approved.

@VSadov VSadov merged commit 04f298c into dotnet:master Jan 18, 2017
@AlekseyTs
Copy link
Contributor

@VSadov Is there a reason for not making similar change, or not testing similar scenarios for VB?

@jcouv
Copy link
Member

jcouv commented Jan 19, 2017

Vlad said he'll file an issue for VB, but the fix is more involved there, so he's not recommending it for RTM.

@jcouv
Copy link
Member

jcouv commented Jan 21, 2017

@VSadov Did you file a follow-up issue for VB?

@VSadov
Copy link
Member Author

VSadov commented May 18, 2017

BTW, the VB bug is
#16698

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

Successfully merging this pull request may close these issues.

Tuple and extension methods
6 participants