Skip to content

Commit 01c628c

Browse files
author
Julien Couvreur
committed
Extensions: Address follow-ups related to collection expressions
1 parent eaeace3 commit 01c628c

File tree

7 files changed

+788
-58
lines changed

7 files changed

+788
-58
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,11 +1467,23 @@ static ImmutableArray<MethodSymbol> filterOutBadGenericMethods(
14671467
{
14681468
// If the method is generic, skip it if the type arguments cannot be inferred.
14691469
var member = candidate.Member;
1470+
if (member.GetIsNewExtensionMember())
1471+
{
1472+
if (member.TryGetCorrespondingExtensionImplementationMethod() is { } extensionImplementation)
1473+
{
1474+
member = extensionImplementation;
1475+
}
1476+
else
1477+
{
1478+
continue;
1479+
}
1480+
}
1481+
14701482
var typeParameters = member.TypeParameters;
14711483

14721484
if (!typeParameters.IsEmpty)
14731485
{
1474-
if (resolution.IsExtensionMethodGroup) // Tracked by https://github.com/dotnet/roslyn/issues/78960 : we need to handle new extension methods
1486+
if (resolution.IsExtensionMethodGroup)
14751487
{
14761488
// We need to validate an ability to infer type arguments as well as check conversion to 'this' parameter.
14771489
// Overload resolution doesn't check the conversion when 'this' type refers to a type parameter
@@ -1522,7 +1534,7 @@ static ImmutableArray<MethodSymbol> filterOutBadGenericMethods(
15221534
parameterTypes,
15231535
parameterRefKinds,
15241536
ImmutableArray.Create<BoundExpression>(methodGroup.ReceiverOpt, new BoundValuePlaceholder(syntax, secondArgumentType) { WasCompilerGenerated = true }),
1525-
ref useSiteInfo); // Tracked by https://github.com/dotnet/roslyn/issues/78960 : we may need to override ordinals here
1537+
ref useSiteInfo);
15261538

15271539
if (!inferenceResult.Success)
15281540
{
@@ -1599,42 +1611,10 @@ static bool bindInvocationExpressionContinued(
15991611
return false;
16001612
}
16011613

1602-
// Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate.
1603-
// We still have to determine if it passes final validation.
1604-
1605-
var methodResult = result.ValidResult;
1606-
var method = methodResult.Member;
1607-
1608-
// Tracked by https://github.com/dotnet/roslyn/issues/78960: It looks like we added a bunch of code in BindInvocationExpressionContinued at this position
1609-
// that specifically deals with new extension methods. It adjusts analyzedArguments, etc.
1610-
// It is very likely we need to do the same here.
1611-
1612-
// It is possible that overload resolution succeeded, but we have chosen an
1613-
// instance method and we're in a static method. A careful reading of the
1614-
// overload resolution spec shows that the "final validation" stage allows an
1615-
// "implicit this" on any method call, not just method calls from inside
1616-
// instance methods. Therefore we must detect this scenario here, rather than in
1617-
// overload resolution.
1618-
1619-
var receiver = methodGroup.Receiver;
1620-
1621-
// Note: we specifically want to do final validation (7.6.5.1) without checking delegate compatibility (15.2),
1622-
// so we're calling MethodGroupFinalValidation directly, rather than via MethodGroupConversionHasErrors.
1623-
// Note: final validation wants the receiver that corresponds to the source representation
1624-
// (i.e. the first argument, if invokedAsExtensionMethod).
1625-
var gotError = addMethodBinder.MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod);
1626-
1627-
addMethodBinder.ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false);
1628-
ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false);
1629-
ReportDiagnosticsIfDisallowedExtension(diagnostics, method, node);
1630-
1631-
// No use site errors, but there could be use site warnings.
1632-
// If there are any use site warnings, they have already been reported by overload resolution.
1633-
Debug.Assert(!method.HasUseSiteError, "Shouldn't have reached this point if there were use site errors.");
1634-
Debug.Assert(!method.IsRuntimeFinalizer());
1635-
1636-
addMethod = method;
1637-
return !gotError;
1614+
// Although this function is modelled after `BindInvocationExpressionContinued`,
1615+
// since `HasCollectionExpressionApplicableAddMethod` uses a placeholder element of type `dynamic`,
1616+
// only the first listed error case can be hit.
1617+
throw ExceptionUtilities.Unreachable();
16381618
}
16391619
}
16401620

@@ -1668,7 +1648,7 @@ internal static BoundExpression GetUnderlyingCollectionExpressionElement(BoundCo
16681648
// Add methods. This case can be hit for spreads and non-spread elements.
16691649
Debug.Assert(call.HasErrors);
16701650
Debug.Assert(call.Method.Name == "Add");
1671-
return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0]; // Tracked by https://github.com/dotnet/roslyn/issues/78960: Add test coverage for new extensions
1651+
return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0];
16721652
case BoundBadExpression badExpression:
16731653
Debug.Assert(false); // Add test if we hit this assert.
16741654
return badExpression;
@@ -1717,7 +1697,7 @@ internal bool TryGetCollectionIterationType(SyntaxNode syntax, TypeSymbol collec
17171697
out iterationType,
17181698
builder: out var builder);
17191699
// Collection expression target types require instance method GetEnumerator.
1720-
if (result && builder.ViaExtensionMethod) // Tracked by https://github.com/dotnet/roslyn/issues/78960: Add test coverage for new extensions
1700+
if (result && builder.ViaExtensionMethod)
17211701
{
17221702
iterationType = default;
17231703
return false;

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4604,7 +4604,7 @@ private MemberAnalysisResult IsApplicable(
46044604
Debug.Assert(
46054605
!forExtensionMethodThisArg ||
46064606
(!conversion.IsDynamic ||
4607-
(ignoreOpenTypes && parameters.ParameterTypes[argumentPosition].Type.ContainsTypeParameter(parameterContainer: (MethodSymbol)candidate))));
4607+
(ignoreOpenTypes && TypeContainsTypeParameterFromContainer(candidate, parameters.ParameterTypes[argumentPosition].Type))));
46084608

46094609
if (forExtensionMethodThisArg && !conversion.IsDynamic && !Conversions.IsValidExtensionMethodThisArgConversion(conversion))
46104610
{
@@ -4702,7 +4702,7 @@ private Conversion CheckArgumentForApplicability(
47024702
// - Then, any parameter whose type is open (i.e. contains a type parameter; see §4.4.2) is elided, along with its corresponding parameter(s).
47034703
// and
47044704
// - The modified parameter list for F is applicable to the modified argument list in terms of section §7.5.3.1
4705-
if (ignoreOpenTypes && parameterType.ContainsTypeParameter(parameterContainer: (MethodSymbol)candidate))
4705+
if (ignoreOpenTypes && TypeContainsTypeParameterFromContainer(candidate, parameterType))
47064706
{
47074707
// defer applicability check to runtime:
47084708
return Conversion.ImplicitDynamic;
@@ -4748,6 +4748,16 @@ private Conversion CheckArgumentForApplicability(
47484748
}
47494749
}
47504750

4751+
private static bool TypeContainsTypeParameterFromContainer(Symbol container, TypeSymbol parameterType)
4752+
{
4753+
if (parameterType.ContainsTypeParameter(parameterContainer: container))
4754+
{
4755+
return true;
4756+
}
4757+
4758+
return container.GetIsNewExtensionMember() && parameterType.ContainsTypeParameter(parameterContainer: container.ContainingType);
4759+
}
4760+
47514761
private static TMember GetConstructedFrom<TMember>(TMember member) where TMember : Symbol
47524762
{
47534763
switch (member.Kind)

src/Compilers/CSharp/Portable/Symbols/TypeMap.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ internal TypeMap(ImmutableArray<TypeParameterSymbol> from, ImmutableArray<TypeWi
4242
: base(ConstructMapping(from, to))
4343
{
4444
// mapping contents are read-only hereafter
45-
Debug.Assert(allowAlpha || !from.Any(static tp => tp is SubstitutedTypeParameterSymbol));
45+
Debug.Assert(allowAlpha || !from.Any(static tp => tp is SubstitutedTypeParameterSymbol && tp.ContainingSymbol is not SourceExtensionImplementationMethodSymbol));
4646
}
4747

4848
// Only when the caller passes allowAlpha=true do we tolerate substituted (alpha-renamed) type parameters as keys

src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ public static bool ContainsTypeParameter(this TypeSymbol type, TypeParameterSymb
12051205
private static readonly Func<TypeSymbol, TypeParameterSymbol?, bool, bool> s_containsTypeParameterPredicate =
12061206
(type, parameter, unused) => type.TypeKind == TypeKind.TypeParameter && (parameter is null || TypeSymbol.Equals(type, parameter, TypeCompareKind.ConsiderEverything2));
12071207

1208-
public static bool ContainsTypeParameter(this TypeSymbol type, MethodSymbol parameterContainer)
1208+
public static bool ContainsTypeParameter(this TypeSymbol type, Symbol parameterContainer)
12091209
{
12101210
RoslynDebug.Assert((object)parameterContainer != null);
12111211

src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,7 @@ public void LanguageVersionAdded_Canary()
17221722
// - [ ] replace all references to C# "Next" (such as `TestOptions.RegularNext` or `LanguageVersionFacts.CSharpNext`) with the new version and fix failing tests
17231723
// - [ ] update _MaxAvailableLangVersion cap (a relevant test should break when new version is introduced)
17241724
// - [ ] update the "UpgradeProject" codefixer
1725+
// - [ ] test VS insertion
17251726
//
17261727
// Other repos also need updates:
17271728
// - [ ] email release management to add to the release notes. See csharp-version in release.json in previous example: https://github.com/dotnet/core/pull/9493

src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36378,12 +36378,12 @@ static class E
3637836378
""";
3637936379
var comp = CreateCompilation(src);
3638036380
comp.VerifyEmitDiagnostics(
36381-
// (2,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension<U>(U).M<T>(T)' requires a receiver of type 'U'
36381+
// (2,1): error CS1973: 'object' has no applicable method named 'M' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
3638236382
// object.M(d);
36383-
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "object").WithArguments("object", "M", "E.extension<U>(U).M<T>(T)", "U").WithLocation(2, 1),
36384-
// (3,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension<U>(U).M<T>(T)' requires a receiver of type 'U'
36383+
Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "object.M(d)").WithArguments("object", "M").WithLocation(2, 1),
36384+
// (3,1): error CS0176: Member 'E.extension<U>(U).M<T>(T)' cannot be accessed with an instance reference; qualify it with a type name instead
3638536385
// new object().M(d);
36386-
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "new object()").WithArguments("object", "M", "E.extension<U>(U).M<T>(T)", "U").WithLocation(3, 1));
36386+
Diagnostic(ErrorCode.ERR_ObjectProhibited, "new object().M").WithArguments("E.extension<U>(U).M<T>(T)").WithLocation(3, 1));
3638736387
}
3638836388

3638936389
[Fact]
@@ -36432,9 +36432,9 @@ static class E
3643236432
""";
3643336433
var comp = CreateCompilation(src);
3643436434
comp.VerifyEmitDiagnostics(
36435-
// (2,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension<U>(U).M(object)' requires a receiver of type 'U'
36435+
// (2,1): error CS1973: 'object' has no applicable method named 'M' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
3643636436
// object.M(d);
36437-
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "object").WithArguments("object", "M", "E.extension<U>(U).M(object)", "U").WithLocation(2, 1),
36437+
Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "object.M(d)").WithArguments("object", "M").WithLocation(2, 1),
3643836438
// (3,1): error CS1973: 'object' has no applicable method named 'M2' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
3643936439
// new object().M2(d);
3644036440
Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "new object().M2(d)").WithArguments("object", "M2").WithLocation(3, 1));
@@ -48538,15 +48538,31 @@ public class MyCollection<T> : IEnumerable<T>
4853848538
IEnumerator IEnumerable.GetEnumerator() => throw null!;
4853948539
}
4854048540
""";
48541-
// https://github.com/dotnet/roslyn/issues/78960
4854248541
var comp = CreateCompilation(src);
48543-
comp.VerifyEmitDiagnostics(
48544-
// (7,26): error CS9215: Collection expression type 'MyCollection<object>' must have an instance or extension method 'Add' that can be called with a single argument.
48545-
// MyCollection<object> c = [oNull, oNotNull];
48546-
Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[oNull, oNotNull]").WithArguments("MyCollection<object>").WithLocation(7, 26),
48547-
// (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>'
48548-
// MyCollection<object> c = [oNull, oNotNull];
48549-
Diagnostic(ErrorCode.ERR_BadInstanceArgType, "[oNull, oNotNull]").WithArguments("MyCollection<object>", "Add", "E.extension<T>(MyCollection<T>).Add(T)", "MyCollection<T>").WithLocation(7, 26));
48542+
comp.VerifyEmitDiagnostics();
48543+
48544+
src = """
48545+
#nullable enable
48546+
using System.Collections;
48547+
using System.Collections.Generic;
48548+
48549+
object? oNull = null;
48550+
object oNotNull = new object();
48551+
MyCollection<object> c = [oNull, oNotNull];
48552+
48553+
static class E
48554+
{
48555+
public static void Add<T>(this MyCollection<T> c, T o) { }
48556+
}
48557+
48558+
public class MyCollection<T> : IEnumerable<T>
48559+
{
48560+
IEnumerator<T> IEnumerable<T>.GetEnumerator() => throw null!;
48561+
IEnumerator IEnumerable.GetEnumerator() => throw null!;
48562+
}
48563+
""";
48564+
comp = CreateCompilation(src);
48565+
comp.VerifyEmitDiagnostics();
4855048566
}
4855148567

4855248568
[Fact]

0 commit comments

Comments
 (0)