Skip to content

Conversation

@jcouv
Copy link
Member

@jcouv jcouv commented Aug 12, 2025

Closes #78960
The first commit addresses minor post-PR-merge follow-ups from last PR: #79702 (comment)

The design needs to be confirmed with LDM, it is outlined here and copied below:

#### Collection expressions

- Extension `Add` works
- Extension `GetEnumerator` works for spread
- Extension `GetEnumerator` does not affect the determination of the element type (must be instance)
- Static `Create` extension methods should not count as a blessed **create** method
- Should extension countable properties affect collection expressions?

#### `params` collections

- Extensions `Add` does not affect what types are allowed with `params`

Note: the last LDM decision regarding pattern-based constructs was to not let extension properties count as "countable properties" in any of the relevant scenarios (list-patterns and implicit indexers) so I kept that behavior here as well.

Relates to test plan #76130

@jcouv jcouv self-assigned this Aug 12, 2025
@jcouv jcouv added Area-Compilers Feature - Extension Everything The extension everything feature labels Aug 12, 2025
{
if (member.TryGetCorrespondingExtensionImplementationMethod() is { } extensionImplementation)
{
member = extensionImplementation;
Copy link
Member Author

@jcouv jcouv Aug 12, 2025

Choose a reason for hiding this comment

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

📝 since we swap the implementation method for the member here and reduce it below, I had to relax the assertion in TypeMap again (see PR)
An alternative would be to implement the logic for this method specially for new extension methods, instead of using the implementation method to leverage the existing handling

@jcouv jcouv force-pushed the extension-collections branch from 01c628c to 083fbaa Compare August 12, 2025 07:27
@jcouv jcouv force-pushed the extension-collections branch from 083fbaa to 03e8cbd Compare August 12, 2025 07:31
@jcouv jcouv marked this pull request as ready for review August 12, 2025 15:29
@jcouv jcouv requested a review from a team as a code owner August 12, 2025 15:29
@jcouv jcouv requested a review from jjonescz August 12, 2025 15:29
{
// If the method is generic, skip it if the type arguments cannot be inferred.
var member = candidate.Member;
if (member.GetIsNewExtensionMember())
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

if (member.GetIsNewExtensionMember())

I think this special handling deserves comment. #Closed

builder: out var builder);
// Collection expression target types require instance method GetEnumerator.
if (result && builder.ViaExtensionMethod) // Tracked by https://github.com/dotnet/roslyn/issues/78960: Add test coverage for new extensions
if (result && builder.ViaExtensionMethod)
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

ViaExtensionMethod

Is this flag set when a new extension is used? #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.

Yes, see getEnumeratorInfo

                if (SatisfiesGetEnumeratorPattern(syntax, collectionSyntax, ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics))
                {
                    return createPatternBasedEnumeratorResult(ref builder, collectionExpr, isAsync, viaExtensionMethod: true, diagnostics);
                }

The relevant test is CollectionExpression_09

{
return ConsiderEverything;
}
else if (comparison == TypeCompareKind.AllIgnoreOptions)
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

else if (comparison == TypeCompareKind.AllIgnoreOptions)

I see no harm in keeping this case. Should we switch to a switch, BTW. #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.

I prefer to remove since not exercised

{
// mapping contents are read-only hereafter
Debug.Assert(allowAlpha || !from.Any(static tp => tp is SubstitutedTypeParameterSymbol));
Debug.Assert(allowAlpha || !from.Any(static tp => tp is SubstitutedTypeParameterSymbol && tp.ContainingSymbol is not SourceExtensionImplementationMethodSymbol));
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

tp.ContainingSymbol is not SourceExtensionImplementationMethodSymbol

I think it would be better to check if the type parameter is original definition. Those should be fine to substitute regardless what the container is and the check would be more robust long term. #Closed

(type, parameter, unused) => type.TypeKind == TypeKind.TypeParameter && (parameter is null || TypeSymbol.Equals(type, parameter, TypeCompareKind.ConsiderEverything2));

public static bool ContainsTypeParameter(this TypeSymbol type, MethodSymbol parameterContainer)
public static bool ContainsTypeParameter(this TypeSymbol type, Symbol parameterContainer)
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

parameterContainer

Consider renaming to "typeParameterContainer". Here and in s_isTypeParameterWithSpecificContainerPredicate initialization #Closed

// (7,26): error CS1929: 'MyCollection<object>' does not contain a definition for 'Add' and the best extension method overload 'E.extension<T>(MyCollection<T>).Add(T)' requires a receiver of type 'MyCollection<T>'
// MyCollection<object> c = [oNull, oNotNull];
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "[oNull, oNotNull]").WithArguments("MyCollection<object>", "Add", "E.extension<T>(MyCollection<T>).Add(T)", "MyCollection<T>").WithLocation(7, 26));
comp.VerifyEmitDiagnostics();
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

comp.VerifyEmitDiagnostics();

Consider executing and observing the result #Closed

}
}

public class D : System.Collections.IEnumerable
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

D

Is this type used? #Closed

{
extension(C c)
{
public int Length => 2;
Copy link
Contributor

@AlekseyTs AlekseyTs Aug 13, 2025

Choose a reason for hiding this comment

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

2

I assume the purpose of the test is to verify that this property isn't used. Let's throw from it then. #Closed

@AlekseyTs
Copy link
Contributor

Done with review pass (commit 2)

}
""";
var comp = CreateCompilationWithIL(src, ilSrc);
comp.VerifyEmitDiagnostics();
Copy link
Member

@jjonescz jjonescz Aug 13, 2025

Choose a reason for hiding this comment

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

Should we verify execution to check that E2 was used, not E? #Resolved

""";
var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (1,7): error CS1921: The best overloaded method match for 'E.extension(C).Add(int)' has wrong signature for the initializer element. The initializable Add must be an accessible instance method.
Copy link
Member

@jjonescz jjonescz Aug 13, 2025

Choose a reason for hiding this comment

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

This error wording seems inaccurate - the Add can be an instance or extension method. But I guess that's a pre-existing issue. #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup. I'll leave as pre-existing issue

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)

@jcouv jcouv merged commit 9138704 into dotnet:main Aug 14, 2025
24 checks passed
@jcouv jcouv deleted the extension-collections branch August 14, 2025 05:26
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Aug 14, 2025
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extensions: follow-up on remaining pattern-based binding scenarios

4 participants