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

Implement name and signature conflict check across extension containers with the same receiver type #77747

Merged
merged 1 commit into from
Mar 25, 2025

Conversation

AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Mar 21, 2025

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

… 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."
@AlekseyTs AlekseyTs added Area-Compilers Feature - Extension Everything The extension everything feature labels Mar 21, 2025
@AlekseyTs AlekseyTs requested review from jjonescz and jcouv March 21, 2025 21:50
@AlekseyTs AlekseyTs requested a review from a team as a code owner March 21, 2025 21:50
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Mar 21, 2025
@AlekseyTs AlekseyTs changed the title Implement name an signature conflict check across extension containers with the same receiver type Implement name and signature conflict check across extension containers with the same receiver type Mar 21, 2025
@AlekseyTs
Copy link
Contributor Author

@jcouv, @jjonescz, @dotnet/roslyn-compiler Please review

@jcouv jcouv self-assigned this Mar 22, 2025
@AlekseyTs
Copy link
Contributor Author

@jcouv, @jjonescz, @dotnet/roslyn-compiler Please review

Comment on lines +2400 to +2401
if (membersByNameToMerge.Count == 0 ||
(membersByNameToMerge.Count == 1 && membersByNameToMerge.Values.Single() is [SynthesizedExtensionMarker]))
Copy link
Member

@jjonescz jjonescz Mar 25, 2025

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?

Suggested change
if (membersByNameToMerge.Count == 0 ||
(membersByNameToMerge.Count == 1 && membersByNameToMerge.Values.Single() is [SynthesizedExtensionMarker]))
if (membersByNameToMerge.Values is [] or [[SynthesizedExtensionMarker]])
``` #Resolved

Copy link
Contributor Author

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.

Copy link
Contributor Author

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!);
Copy link
Member

@jjonescz jjonescz Mar 25, 2025

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, I think:

Suggested change
return Equals((ExtensionGroupingKey)obj!);
return obj is ExtensionGroupingKey k && Equals(k);
``` #Resolved

Copy link
Contributor Author

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();
Copy link
Member

@jjonescz jjonescz Mar 25, 2025

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 ExtensionGroupingKeys that are equal by Equals have different hash code (because the receiver types have nullability annotations differences for example). #Resolved

Copy link
Contributor Author

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());
Copy link
Member

@jjonescz jjonescz Mar 25, 2025

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

Copy link
Contributor Author

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.

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() {}
Copy link
Member

@jjonescz jjonescz Mar 25, 2025

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

Copy link
Contributor Author

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 AlekseyTs merged commit 67a009f into dotnet:features/extensions Mar 25, 2025
24 checks passed
@jjonescz
Copy link
Member

@AlekseyTs did you want to wait for @jcouv's review here as well before merging?

@AlekseyTs
Copy link
Contributor Author

did you want to wait for @jcouv's review here as well before merging?

Yes, my mistake, thank you for pointing out. I'll make sure to get a sign off from @jcouv and follow-up on any feedback.

@@ -1964,17 +1963,21 @@ private void CheckRecordMemberNames(BindingDiagnosticBag diagnostics)
}
}

private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics)
private static void CheckMemberNameConflicts(
SourceMemberContainerTypeSymbol containerForDiagnostics,
Copy link
Member

@jcouv jcouv Mar 25, 2025

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

Copy link
Contributor Author

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

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.

@jcouv
Copy link
Member

jcouv commented Mar 25, 2025

LGTM Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature - Extension Everything The extension everything feature untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants