-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Extensions: relax inferrability rule #78758
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
Conversation
550cc8e to
8de7718
Compare
8de7718 to
ace0477
Compare
| return parameter; | ||
| } | ||
|
|
||
| static void checkUnderspecifiedGenericExtension(ParameterSymbol parameter, ImmutableArray<TypeParameterSymbol> typeParameters, BindingDiagnosticBag diagnostics) |
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.
📝 moved this logic to (out-of-date after new LDM decision)SourceNamedTypeSymbol.AfterMembersCompletedChecks as the added GetMembers call was causing a circularity issue
|
|
||
| void checkUnderspecifiedGenericExtension(ParameterSymbol parameter, ImmutableArray<TypeParameterSymbol> typeParameters, BindingDiagnosticBag diagnostics) | ||
| { | ||
| if (GetMembers().All((m, a) => m is MethodSymbol { MethodKind: MethodKind.Ordinary }, arg: (object?)null)) |
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.
|
|
||
| if (TryGetOrCreateExtensionMarker() is { Parameters: [var extensionParameter] }) | ||
| { | ||
| checkUnderspecifiedGenericExtension(extensionParameter, TypeParameters, diagnostics); |
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.
I think that long term it would be better to perform this check per member and report an error per member. For example, once we enable indexers, I can imagine that some indexer declarations (referencing remaining type arguments in their parameters) will be allowed, and others won't be allowed. #Closed
|
|
||
| void checkUnderspecifiedGenericExtension(ParameterSymbol parameter, ImmutableArray<TypeParameterSymbol> typeParameters, BindingDiagnosticBag diagnostics) | ||
| { | ||
| if (GetMembers().All((m, a) => m is MethodSymbol { MethodKind: MethodKind.Ordinary }, arg: (object?)null)) |
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.
I think operators should be allowed too. At least, consider leaving a comment to follow up, the one that starts with a link to the test plan. Operators are being worked on as we speak. BTW, similar to indexers, I think that some operator declarations would be allowed and other operator declarations wouldn't be allowed. This is even stronger reason to moving to per member check/reporting. #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.
The criteria we last discussed in WG is whether it's possible to use the member with explicit type arguments.
So I don't think we'll allow either indexers or operators in non-inferrable extension blocks.
FWIW, here's the update I captured in the spec (to be reviewed in LDM this week):
__Inferrability:__ All the type parameters of an extension block must be used in the receiver type when the extension block
contains a non-method member.
This makes it always possible to infer the type arguments when applied to a receiver of the given receiver type and
the member doesn't allow explicit type arguments.
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.
I do see that in the summary, but, to be honest, I do not remember us having a serious discussion about the criteria to the point that I must have forgotten us deciding on having the restriction. At the same time, from our "Tensors and extension operators" meeting I vaguely remember us expressing a desire to support the following scenario from #78472 (comment):
extension<TTensor, TScalar>(TTensor)
where TTensor : IReadOnlyTensor<TTensor, TScalar>
where TScalar : IAdditionOperators<TScalar, TScalar, TScalar>
{
public static TTensor operator +(TTensor left, TScalar right);
}
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.
Here is a link to the specific summary message from Tanner specifically mentioning the following shape as planned for .NET 10:
extension<TTensor, TScalar>(TTensor)
where TTensor : IReadOnlyTensor<TTensor, TScalar>
where TScalar : IAdditionOperators<TScalar, TScalar, TScalar>
{
public static TTensor operator +(TTensor left, TScalar right);
}
|
|
||
| var comp = CreateCompilation(src); | ||
| comp.VerifyEmitDiagnostics( | ||
| // (1,9): error CS9286: 'object' does not contain a definition for 'M' and no accessible extension member 'M' for receiver of type 'object' could be found (are you missing a using directive or an assembly reference?) |
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.
Yes, we have a general tracking comment in ResolveExtensions: "// Tracked by #76130 : we'll want to describe what went wrong in a useful way (see OverloadResolutionResult.ReportDiagnostics)"
I'll also add one for this specific test
| Diagnostic(ErrorCode.ERR_ExtensionResolutionFailed, "object.M").WithArguments("object", "M").WithLocation(4, 19)); | ||
|
|
||
| src = """ | ||
| var x = I.M; // binds to I1.M (method) |
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) #Closed |
AlekseyTs
left a comment
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 3). However, I personally think that the implemented rule is unnecessarily restrictive. Therefore, I suggest delaying the merge until after June 4th LDM, where I hope we finalize the design.
| parameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); | ||
| } | ||
|
|
||
| foreach (var typeParameter in extension.TypeParameters) |
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.
| extensionParameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); | ||
| foreach (var parameter in parameters) | ||
| { | ||
| parameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); |
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 usedTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance(); | ||
| extensionParameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); |
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 usedTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance(); | ||
| extensionParameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); |
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.
Agreed. I could leave an follow-up comment in "optimization/MQ" bucket, but I think I'm fine to leave as-is
|
Done with review pass (commit 5) #Closed |
|
@333fred second reviewer |
|
|
||
| /// <summary> | ||
| /// For the type representing an extension declaration, returns the receiver parameter symbol. | ||
| /// Note: this may be null even if <see cref="IsExtension"/> is true, in error cases. |
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.
📝 I'd already added that comment to the corresponding public API, but it seems useful here too
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.
Think you should add some remarks to indicate that it will not be null in the case of extension(SomeType), just an unnamed parameter.
| parameter.Type.VisitType(collectTypeParameters, arg: usedTypeParameters); | ||
| } | ||
|
|
||
| if (usedTypeParameters.Count == extensionArity && extensionMemberArity == 0) |
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.
AlekseyTs
left a comment
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 6), with an optimization suggestion #78758 (review)
|
@333fred for second review. Thanks |
| return; | ||
| } | ||
|
|
||
| var usedTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance(); |
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.
Does this need some kind of comparison flags? Do we have tests where the type parameter differs by nullability? #Resolved
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.
Does this need some kind of comparison flags? Do we have tests where the type parameter differs by nullability?
We are dealing with declarations, therefore, we don't need to worry about any substitution/alpha renaming going on.
|
|
||
| /// <summary> | ||
| /// For the type representing an extension declaration, returns the receiver parameter symbol. | ||
| /// Note: this may be null even if <see cref="IsExtension"/> is true, in error cases. |
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.
Think you should add some remarks to indicate that it will not be null in the case of extension(SomeType), just an unnamed parameter.
| </data> | ||
| <data name="ERR_UnderspecifiedExtension" xml:space="preserve"> | ||
| <value>The extended type '{0}' must reference all the type parameters declared by the extension, but type parameter '{1}' is not referenced.</value> | ||
| <value>Every type parameter from the extension block must be referenced by the extension parameter or a parameter of this member. But type parameter `{0}` is not referenced.</value> |
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.
I think this is far too wordy.
| <value>Every type parameter from the extension block must be referenced by the extension parameter or a parameter of this member. But type parameter `{0}` is not referenced.</value> | |
| <value>The type parameter `{0}` is not referenced by either the extension parameter or a parameter of this member.</value> |
From discussion with WG, relaxing this restriction allows for greater portability of classic extension methods. But we're keeping the check for members that can't be called with explicit type arguments.To be confirmed with LDM next week.New rule: https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md#extension-declaration-validation
Closes #78472
Relates to test plan #76130