-
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
Implement name and signature conflict check across extension containers with the same receiver type #77747
Conversation
… with the same receiver type Implements the following rule from the spec: "Within a given enclosing static class, the set of extension member declarations with the same receiver type (modulo identity conversion and type parameter name substitution) are treated as a single declaration space similar to the members within a class or struct declaration, and are subject to the same rules about uniqueness."
if (membersByNameToMerge.Count == 0 || | ||
(membersByNameToMerge.Count == 1 && membersByNameToMerge.Values.Single() is [SynthesizedExtensionMarker])) |
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.
nit: could this be simplified to something like the following?
if (membersByNameToMerge.Count == 0 || | |
(membersByNameToMerge.Count == 1 && membersByNameToMerge.Values.Single() is [SynthesizedExtensionMarker])) | |
if (membersByNameToMerge.Values is [] or [[SynthesizedExtensionMarker]]) | |
``` #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.
nit: could this be simplified to something like the following?
Even if the suggested expression has equivalent behavior, I do not find it simpler or easier to follow.
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.
BTW, the suggested code doesn't compile:
error CS0021: Cannot apply indexing with [] to an expression of type 'Dictionary<ReadOnlyMemory<char>, ImmutableArray<Symbol>>.ValueCollection'
error CS0021: Cannot apply indexing with [] to an expression of type 'Dictionary<ReadOnlyMemory<char>, ImmutableArray<Symbol>>.ValueCollection'
public override bool Equals([NotNullWhen(true)] object? obj) | ||
{ | ||
Debug.Assert(false); // Usage of this method is unexpected | ||
return Equals((ExtensionGroupingKey)obj!); |
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.
Conforming Equals override should handle null
s, I think:
return Equals((ExtensionGroupingKey)obj!); | |
return obj is ExtensionGroupingKey k && Equals(k); | |
``` #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.
Conforming Equals override should handle nulls,
In general, this is true. However we control usage of this type and don't want it to be boxed for the purpose of comparison. Therefore, we not only do not expect it to be called with null
, we do not expected it to be called ever. Hence the assert above.
|
||
public override int GetHashCode() | ||
{ | ||
return ReceiverType.GetHashCode(); |
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.
Shouldn't TypeCompareKind.AllIgnoreOptions
be taken into consideration here as well? Otherwise it might happen that two ExtensionGroupingKey
s that are equal by Equals
have different hash code (because the receiver types have nullability annotations differences for example). #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.
Shouldn't
TypeCompareKind.AllIgnoreOptions
be taken into consideration here as well?
It should be and it is taken into consideration. Implementations of GetHashCode
for symbols are supposed to ignore any differences that could be ignored during symbol comparison. That significantly simplifies code in here and in SymbolEqualityComparer
, for example.
} | ||
else | ||
{ | ||
membersUnordered = membersUnordered.Concat(extension.GetMembersUnordered()); |
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 moving this inside concatMembers
as it's otherwise hard to follow why is this done here only when extension.Arity == 0
. #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.
Consider moving this inside
concatMembers
as it's otherwise hard to follow why is this done here only whenextension.Arity == 0
.
Doing so would lead to an incorrect behavior, specifically the resulting array would contain duplicates because concatMembers
is called in a loop several times. The set of members concatenated here and the set of members added to the same array in concatMembers
are different in a general case.
|
||
comp.VerifyDiagnostics( | ||
// (10,21): error CS0111: Type 'Extensions' already defines a member called 'M1' with the same parameter types | ||
// static void M1() {} |
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 adding marker comments like // 1
(or perhaps #line
pragmas) otherwise it's not very clear which line this diagnostic is reported at. Applies also to similar cases in new tests. #WontFix
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.
Usually the source on the error lines is unique and/or the test scenarios are pretty small.
@AlekseyTs did you want to wait for @jcouv's review here as well before merging? |
@@ -1964,17 +1963,21 @@ private void CheckRecordMemberNames(BindingDiagnosticBag diagnostics) | |||
} | |||
} | |||
|
|||
private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics) | |||
private static void CheckMemberNameConflicts( | |||
SourceMemberContainerTypeSymbol containerForDiagnostics, |
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.
nit: I didn't follow why this method was changed to static. The only value passed to containerForDiagnostics
is this
.
The same applies to CheckIndexerNameConflicts
and ReportMethodSignatureCollision
and possibly others 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.
nit: I didn't follow why this method was changed to static. The only value passed to
containerForDiagnostics
isthis
. The same applies toCheckIndexerNameConflicts
andReportMethodSignatureCollision
There are additional parameters passed as well. While at the moment the only value passed to containerForDiagnostics
is this
, it doesn't have to be this
, the logic doesn't depend on the fact. The goal was to make sure that the methods do not accidentally use any instance API any instance data, they should operate only on the information passed in explicitly and shouldn't assume that the passed in symbols directly declared in this
.
LGTM Thanks |
Implements the following rule from the spec:
"Within a given enclosing static class, the set of extension member declarations with the same receiver type (modulo identity conversion and type parameter name substitution) are treated as a single declaration space similar to the members within a class or struct declaration, and are subject to the same rules about uniqueness."
Relates to test plan #76130