diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 70bd7b97240b..c5948108b504 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -1467,11 +1467,25 @@ static ImmutableArray filterOutBadGenericMethods( { // If the method is generic, skip it if the type arguments cannot be inferred. var member = candidate.Member; + + // For new extension methods, we'll use the extension implementation method to determine inferrability + if (member.GetIsNewExtensionMember()) + { + if (member.TryGetCorrespondingExtensionImplementationMethod() is { } extensionImplementation) + { + member = extensionImplementation; + } + else + { + continue; + } + } + var typeParameters = member.TypeParameters; if (!typeParameters.IsEmpty) { - if (resolution.IsExtensionMethodGroup) // Tracked by https://github.com/dotnet/roslyn/issues/78960 : we need to handle new extension methods + if (resolution.IsExtensionMethodGroup) { // We need to validate an ability to infer type arguments as well as check conversion to 'this' parameter. // Overload resolution doesn't check the conversion when 'this' type refers to a type parameter @@ -1522,7 +1536,7 @@ static ImmutableArray filterOutBadGenericMethods( parameterTypes, parameterRefKinds, ImmutableArray.Create(methodGroup.ReceiverOpt, new BoundValuePlaceholder(syntax, secondArgumentType) { WasCompilerGenerated = true }), - ref useSiteInfo); // Tracked by https://github.com/dotnet/roslyn/issues/78960 : we may need to override ordinals here + ref useSiteInfo); if (!inferenceResult.Success) { @@ -1599,42 +1613,10 @@ static bool bindInvocationExpressionContinued( return false; } - // Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate. - // We still have to determine if it passes final validation. - - var methodResult = result.ValidResult; - var method = methodResult.Member; - - // Tracked by https://github.com/dotnet/roslyn/issues/78960: It looks like we added a bunch of code in BindInvocationExpressionContinued at this position - // that specifically deals with new extension methods. It adjusts analyzedArguments, etc. - // It is very likely we need to do the same here. - - // It is possible that overload resolution succeeded, but we have chosen an - // instance method and we're in a static method. A careful reading of the - // overload resolution spec shows that the "final validation" stage allows an - // "implicit this" on any method call, not just method calls from inside - // instance methods. Therefore we must detect this scenario here, rather than in - // overload resolution. - - var receiver = methodGroup.Receiver; - - // Note: we specifically want to do final validation (7.6.5.1) without checking delegate compatibility (15.2), - // so we're calling MethodGroupFinalValidation directly, rather than via MethodGroupConversionHasErrors. - // Note: final validation wants the receiver that corresponds to the source representation - // (i.e. the first argument, if invokedAsExtensionMethod). - var gotError = addMethodBinder.MemberGroupFinalValidation(receiver, method, expression, diagnostics, invokedAsExtensionMethod); - - addMethodBinder.ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false); - ReportDiagnosticsIfUnmanagedCallersOnly(diagnostics, method, node, isDelegateConversion: false); - ReportDiagnosticsIfDisallowedExtension(diagnostics, method, node); - - // No use site errors, but there could be use site warnings. - // If there are any use site warnings, they have already been reported by overload resolution. - Debug.Assert(!method.HasUseSiteError, "Shouldn't have reached this point if there were use site errors."); - Debug.Assert(!method.IsRuntimeFinalizer()); - - addMethod = method; - return !gotError; + // Although this function is modelled after `BindInvocationExpressionContinued`, + // since `HasCollectionExpressionApplicableAddMethod` uses a placeholder element of type `dynamic`, + // only the first listed error case can be hit. + throw ExceptionUtilities.Unreachable(); } } @@ -1668,7 +1650,7 @@ internal static BoundExpression GetUnderlyingCollectionExpressionElement(BoundCo // Add methods. This case can be hit for spreads and non-spread elements. Debug.Assert(call.HasErrors); Debug.Assert(call.Method.Name == "Add"); - return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0]; // Tracked by https://github.com/dotnet/roslyn/issues/78960: Add test coverage for new extensions + return call.Arguments[call.InvokedAsExtensionMethod ? 1 : 0]; case BoundBadExpression badExpression: Debug.Assert(false); // Add test if we hit this assert. return badExpression; @@ -1717,7 +1699,7 @@ internal bool TryGetCollectionIterationType(SyntaxNode syntax, TypeSymbol collec out iterationType, 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) { iterationType = default; return false; diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 9e1459d100ff..a32ef07924dd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -4604,7 +4604,7 @@ private MemberAnalysisResult IsApplicable( Debug.Assert( !forExtensionMethodThisArg || (!conversion.IsDynamic || - (ignoreOpenTypes && parameters.ParameterTypes[argumentPosition].Type.ContainsTypeParameter(parameterContainer: (MethodSymbol)candidate)))); + (ignoreOpenTypes && TypeContainsTypeParameterFromContainer(candidate, parameters.ParameterTypes[argumentPosition].Type)))); if (forExtensionMethodThisArg && !conversion.IsDynamic && !Conversions.IsValidExtensionMethodThisArgConversion(conversion)) { @@ -4702,7 +4702,7 @@ private Conversion CheckArgumentForApplicability( // - 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). // and // - The modified parameter list for F is applicable to the modified argument list in terms of section §7.5.3.1 - if (ignoreOpenTypes && parameterType.ContainsTypeParameter(parameterContainer: (MethodSymbol)candidate)) + if (ignoreOpenTypes && TypeContainsTypeParameterFromContainer(candidate, parameterType)) { // defer applicability check to runtime: return Conversion.ImplicitDynamic; @@ -4748,6 +4748,16 @@ private Conversion CheckArgumentForApplicability( } } + private static bool TypeContainsTypeParameterFromContainer(Symbol container, TypeSymbol parameterType) + { + if (parameterType.ContainsTypeParameter(typeParameterContainer: container)) + { + return true; + } + + return container.GetIsNewExtensionMember() && parameterType.ContainsTypeParameter(typeParameterContainer: container.ContainingType); + } + private static TMember GetConstructedFrom(TMember member) where TMember : Symbol { switch (member.Kind) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ExtensionGroupingInfo.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ExtensionGroupingInfo.cs index 9d1f2c5224ad..261e51378d21 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ExtensionGroupingInfo.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ExtensionGroupingInfo.cs @@ -222,7 +222,7 @@ internal static bool HaveSameILSignature(SourceNamedTypeSymbol extension1, Sourc TypeMap? typeMap1 = MemberSignatureComparer.GetTypeMap(extension1); TypeMap? typeMap2 = MemberSignatureComparer.GetTypeMap(extension2); if (extension1.Arity > 0 - && !MemberSignatureComparer.HaveSameConstraints(extension1.TypeParameters, typeMap1, extension2.TypeParameters, typeMap2, TypeCompareKind.AllIgnoreOptions)) + && !MemberSignatureComparer.HaveSameConstraints(extension1.TypeParameters, typeMap1, extension2.TypeParameters, typeMap2, TypeCompareKind.CLRSignatureCompareOptions)) { return false; } @@ -236,7 +236,7 @@ internal static bool HaveSameILSignature(SourceNamedTypeSymbol extension1, Sourc if (!MemberSignatureComparer.HaveSameParameterType(parameter1, typeMap1, parameter2, typeMap2, refKindCompareMode: MemberSignatureComparer.RefKindCompareMode.IgnoreRefKind, - considerDefaultValues: false, TypeCompareKind.AllIgnoreOptions)) + considerDefaultValues: false, TypeCompareKind.CLRSignatureCompareOptions)) { return false; } diff --git a/src/Compilers/CSharp/Portable/Symbols/SymbolEqualityComparer.cs b/src/Compilers/CSharp/Portable/Symbols/SymbolEqualityComparer.cs index b8d882fc2d82..73d9e629ed09 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SymbolEqualityComparer.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SymbolEqualityComparer.cs @@ -43,25 +43,20 @@ private SymbolEqualityComparer(TypeCompareKind comparison) internal static EqualityComparer Create(TypeCompareKind comparison) { - if (comparison == (TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) + switch (comparison) { - return IgnoringDynamicTupleNamesAndNullability; + case TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes: + return IgnoringDynamicTupleNamesAndNullability; + case TypeCompareKind.ConsiderEverything: + return ConsiderEverything; + case TypeCompareKind.AllIgnoreOptionsPlusNullableWithObliviousMatchesAny: + return AllIgnoreOptionsPlusNullableWithUnknownMatchesAny; + case TypeCompareKind.CLRSignatureCompareOptions: + return CLRSignature; + default: + Debug.Assert(false, "Consider optimizing as above when we need to handle a new type comparison kind."); + return new SymbolEqualityComparer(comparison); } - else if (comparison == TypeCompareKind.ConsiderEverything) - { - return ConsiderEverything; - } - else if (comparison == TypeCompareKind.AllIgnoreOptions) - { - return AllIgnoreOptions; - } - else if (comparison == TypeCompareKind.AllIgnoreOptionsPlusNullableWithObliviousMatchesAny) - { - return AllIgnoreOptionsPlusNullableWithUnknownMatchesAny; - } - - Debug.Assert(false, "Consider optimizing as above when we need to handle a new type comparison kind."); - return new SymbolEqualityComparer(comparison); } public override int GetHashCode(Symbol obj) diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs b/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs index 739d238dc3cd..c74b5058721a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeMap.cs @@ -42,7 +42,7 @@ internal TypeMap(ImmutableArray from, ImmutableArray tp is SubstitutedTypeParameterSymbol)); + Debug.Assert(allowAlpha || from.All(static tp => tp.IsDefinition)); } // Only when the caller passes allowAlpha=true do we tolerate substituted (alpha-renamed) type parameters as keys diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 0bbd226cf71c..d0735e63504f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -1205,16 +1205,16 @@ public static bool ContainsTypeParameter(this TypeSymbol type, TypeParameterSymb private static readonly Func s_containsTypeParameterPredicate = (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 typeParameterContainer) { - RoslynDebug.Assert((object)parameterContainer != null); + RoslynDebug.Assert((object)typeParameterContainer != null); - var result = type.VisitType(s_isTypeParameterWithSpecificContainerPredicate, parameterContainer); + var result = type.VisitType(s_isTypeParameterWithSpecificContainerPredicate, typeParameterContainer); return result is object; } private static readonly Func s_isTypeParameterWithSpecificContainerPredicate = - (type, parameterContainer, unused) => type.TypeKind == TypeKind.TypeParameter && (object)type.ContainingSymbol == (object)parameterContainer; + (type, typeParameterContainer, unused) => type.TypeKind == TypeKind.TypeParameter && (object)type.ContainingSymbol == (object)typeParameterContainer; public static bool ContainsTypeParameters(this TypeSymbol type, HashSet parameters) { diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 8016f782e776..32a2813bafcb 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -36378,12 +36378,12 @@ static class E """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (2,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension(U).M(T)' requires a receiver of type 'U' + // (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. // object.M(d); - Diagnostic(ErrorCode.ERR_BadInstanceArgType, "object").WithArguments("object", "M", "E.extension(U).M(T)", "U").WithLocation(2, 1), - // (3,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension(U).M(T)' requires a receiver of type 'U' + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "object.M(d)").WithArguments("object", "M").WithLocation(2, 1), + // (3,1): error CS0176: Member 'E.extension(U).M(T)' cannot be accessed with an instance reference; qualify it with a type name instead // new object().M(d); - Diagnostic(ErrorCode.ERR_BadInstanceArgType, "new object()").WithArguments("object", "M", "E.extension(U).M(T)", "U").WithLocation(3, 1)); + Diagnostic(ErrorCode.ERR_ObjectProhibited, "new object().M").WithArguments("E.extension(U).M(T)").WithLocation(3, 1)); } [Fact] @@ -36432,9 +36432,9 @@ static class E """; var comp = CreateCompilation(src); comp.VerifyEmitDiagnostics( - // (2,1): error CS1929: 'object' does not contain a definition for 'M' and the best extension method overload 'E.extension(U).M(object)' requires a receiver of type 'U' + // (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. // object.M(d); - Diagnostic(ErrorCode.ERR_BadInstanceArgType, "object").WithArguments("object", "M", "E.extension(U).M(object)", "U").WithLocation(2, 1), + Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "object.M(d)").WithArguments("object", "M").WithLocation(2, 1), // (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. // new object().M2(d); Diagnostic(ErrorCode.ERR_BadArgTypeDynamicExtension, "new object().M2(d)").WithArguments("object", "M2").WithLocation(3, 1)); @@ -48528,7 +48528,7 @@ static class E { extension(MyCollection c) { - public void Add(T o) { } + public void Add(T o) { System.Console.Write(o is null ? "True " : "False "); } } } @@ -48538,15 +48538,31 @@ public class MyCollection : IEnumerable IEnumerator IEnumerable.GetEnumerator() => throw null!; } """; - // https://github.com/dotnet/roslyn/issues/78960 var comp = CreateCompilation(src); - comp.VerifyEmitDiagnostics( - // (7,26): error CS9215: Collection expression type 'MyCollection' must have an instance or extension method 'Add' that can be called with a single argument. - // MyCollection c = [oNull, oNotNull]; - Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[oNull, oNotNull]").WithArguments("MyCollection").WithLocation(7, 26), - // (7,26): error CS1929: 'MyCollection' does not contain a definition for 'Add' and the best extension method overload 'E.extension(MyCollection).Add(T)' requires a receiver of type 'MyCollection' - // MyCollection c = [oNull, oNotNull]; - Diagnostic(ErrorCode.ERR_BadInstanceArgType, "[oNull, oNotNull]").WithArguments("MyCollection", "Add", "E.extension(MyCollection).Add(T)", "MyCollection").WithLocation(7, 26)); + CompileAndVerify(comp, expectedOutput: "True False").VerifyDiagnostics(); + + src = """ +#nullable enable +using System.Collections; +using System.Collections.Generic; + +object? oNull = null; +object oNotNull = new object(); +MyCollection c = [oNull, oNotNull]; + +static class E +{ + public static void Add(this MyCollection c, T o) { System.Console.Write(o is null ? "True " : "False "); } +} + +public class MyCollection : IEnumerable +{ + IEnumerator IEnumerable.GetEnumerator() => throw null!; + IEnumerator IEnumerable.GetEnumerator() => throw null!; +} +"""; + comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "True False").VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs index 7d9473e78ae4..3b855fba726c 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs @@ -28872,5 +28872,781 @@ static class E // extension(object) Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "object").WithArguments("System.Object").WithLocation(3, 15)); } + + [Fact] + public void CollectionExpression_01() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public void Add(int i) { System.Console.Write(i); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_02() + { + var src = """ +C c = [1]; + +static class E +{ + extension(C c) + { + public void Add(int i) { System.Console.Write(i); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,7): error CS9215: Collection expression type 'C' must have an instance or extension method 'Add' that can be called with a single argument. + // C c = [1]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1]").WithArguments("C").WithLocation(1, 7)); + } + + [Fact] + public void CollectionExpression_03() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public void Add(T t) { System.Console.Write(t); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_04() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public void Add(int i) { System.Console.Write(i); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,7): error CS9215: Collection expression type 'C' must have an instance or extension method 'Add' that can be called with a single argument. + // C c = [1, 2]; + Diagnostic(ErrorCode.ERR_CollectionExpressionMissingAdd, "[1, 2]").WithArguments("C").WithLocation(1, 7)); + } + + [Fact] + public void CollectionExpression_05() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public void Add(T t) { System.Console.Write(t); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_06() + { + // Based on the following, but without an implementation method + //public static class E + //{ + // extension(C c) + // { + // public void Add(T t) { } + // } + //} + //public class C : System.Collections.IEnumerable + //{ + // public System.Collections.IEnumerator GetEnumerator() => null; + //} + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname '$9794DAFCCB9E752B29BFD6350ADA77F2' + extends System.Object + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi abstract sealed specialname '$73AD9F89912BC4337338E3DE7182B785' + extends System.Object + { + .method public hidebysig specialname static void '$' ( class C c ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + ret + } + } + .method public hidebysig instance void Add ( !!T t ) cil managed + { + .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( + 01 00 24 3c 4d 3e 24 37 33 41 44 39 46 38 39 39 + 31 32 42 43 34 33 33 37 33 33 38 45 33 44 45 37 + 31 38 32 42 37 38 35 00 00 + ) + ldnull + throw + } + } +} +.class public auto ansi beforefieldinit C + extends System.Object + implements [mscorlib]System.Collections.IEnumerable +{ + .method public final hidebysig newslot virtual instance class [mscorlib]System.Collections.IEnumerator GetEnumerator () cil managed + { + ldnull + ret + } + .method public hidebysig specialname rtspecialname instance void .ctor () cil managed + { + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } +} +""" + ExtensionMarkerAttributeIL; + + var src = """ +C c = [1]; +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + comp.VerifyEmitDiagnostics( + // (1,7): error CS0570: 'E.extension(C).Add(T)' is not supported by the language + // C c = [1]; + Diagnostic(ErrorCode.ERR_BindToBogus, "[1]").WithArguments("E.extension(C).Add(T)").WithLocation(1, 7)); + } + + [Fact] + public void CollectionExpression_07() + { + // Based on the following, but without an implementation method + //public static class E + //{ + // extension(C c) + // { + // public void Add(T t) { } + // } + //} + //public class C : System.Collections.IEnumerable + //{ + // public System.Collections.IEnumerator GetEnumerator() => null; + //} + var ilSrc = """ +.class public auto ansi abstract sealed beforefieldinit E + extends System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi sealed specialname '$9794DAFCCB9E752B29BFD6350ADA77F2' + extends System.Object + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) + .class nested public auto ansi abstract sealed specialname '$73AD9F89912BC4337338E3DE7182B785' + extends System.Object + { + .method public hidebysig specialname static void '$' ( class C c ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + ret + } + } + .method public hidebysig instance void Add ( !!T t ) cil managed + { + .custom instance void System.Runtime.CompilerServices.ExtensionMarkerAttribute::.ctor(string) = ( + 01 00 24 3c 4d 3e 24 37 33 41 44 39 46 38 39 39 + 31 32 42 43 34 33 33 37 33 33 38 45 33 44 45 37 + 31 38 32 42 37 38 35 00 00 + ) + ldnull + throw + } + } +} +.class public auto ansi beforefieldinit C + extends System.Object + implements [mscorlib]System.Collections.IEnumerable +{ + .method public final hidebysig newslot virtual instance class [mscorlib]System.Collections.IEnumerator GetEnumerator () cil managed + { + ldnull + ret + } + .method public hidebysig specialname rtspecialname instance void .ctor () cil managed + { + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } +} +""" + ExtensionMarkerAttributeIL; + + var src = """ +C c = [1]; + +public static class E2 +{ + extension(C c) + { + public void Add(int i) { System.Console.Write("ran"); } + } +} +"""; + var comp = CreateCompilationWithIL(src, ilSrc); + CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_08() + { + var src = """ +int[] i = new[] { 1, 2 }; +C c = /**/ [1, .. i] /**/; + +static class E1 +{ + extension(C c) + { + public void Add(int i) { } + } +} + +static class E2 +{ + extension(C c) + { + public void Add(int i) { } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (2,19): error CS0121: The call is ambiguous between the following methods or properties: 'E1.extension(C).Add(int)' and 'E2.extension(C).Add(int)' + // C c = /**/ [1, .. i] /**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "1").WithArguments("E1.extension(C).Add(int)", "E2.extension(C).Add(int)").WithLocation(2, 19), + // (2,25): error CS0121: The call is ambiguous between the following methods or properties: 'E1.extension(C).Add(int)' and 'E2.extension(C).Add(int)' + // C c = /**/ [1, .. i] /**/; + Diagnostic(ErrorCode.ERR_AmbigCall, "i").WithArguments("E1.extension(C).Add(int)", "E2.extension(C).Add(int)").WithLocation(2, 25)); + + VerifyOperationTreeForTest(comp, """ +ICollectionExpressionOperation (2 elements, ConstructMethod: C..ctor()) (OperationKind.CollectionExpression, Type: C, IsInvalid) (Syntax: '[1, .. i]') + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1, IsInvalid) (Syntax: '1') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null, IsInvalid) (Syntax: '.. i') + Operand: + ILocalReferenceOperation: i (OperationKind.LocalReference, Type: System.Int32[], IsInvalid) (Syntax: 'i') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) +"""); + } + + [Fact] + public void CollectionExpression_09() + { + // Element type cannot be determined from a GetEnumerator extension method + // new extension + string src = """ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +MyCollection c = []; + +[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] +class MyCollection +{ +} + +class MyCollectionBuilder +{ + public static MyCollection Create(ReadOnlySpan items) => default; +} + +static class E +{ + extension(MyCollection c) + { + public IEnumerator GetEnumerator() => default; + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (5,23): error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type. + // MyCollection c = []; + Diagnostic(ErrorCode.ERR_CollectionBuilderNoElementType, "[]").WithArguments("MyCollection").WithLocation(5, 23)); + + // classic extension + src = """ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +MyCollection c = []; + +[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] +class MyCollection +{ +} + +class MyCollectionBuilder +{ + public static MyCollection Create(ReadOnlySpan items) => default; +} + +static class E +{ + public static IEnumerator GetEnumerator(this MyCollection c) => default; +} +"""; + comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (5,23): error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type. + // MyCollection c = []; + Diagnostic(ErrorCode.ERR_CollectionBuilderNoElementType, "[]").WithArguments("MyCollection").WithLocation(5, 23)); + + // non-extension method + src = """ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +MyCollection c = []; + +[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] +class MyCollection +{ + public IEnumerator GetEnumerator() => default; +} + +class MyCollectionBuilder +{ + public static MyCollection Create(ReadOnlySpan items) => default; +} + +static class E +{ + extension(MyCollection c) + { + } +} +"""; + comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void CollectionExpression_10() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public static void Add(int i) { } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + 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. + // C c = [1, 2]; + Diagnostic(ErrorCode.ERR_InitializerAddHasWrongSignature, "[1, 2]").WithArguments("E.extension(C).Add(int)").WithLocation(1, 7)); + } + + [Fact] + public void CollectionExpression_11() + { + var src = """ +C c = [1, 2]; + +static class E +{ + extension(C c) + { + public static void Add() { } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (1,7): error CS1501: No overload for method 'Add' takes 1 arguments + // C c = [1, 2]; + Diagnostic(ErrorCode.ERR_BadArgCount, "[1, 2]").WithArguments("Add", "1").WithLocation(1, 7)); + } + + [Fact] + public void CollectionExpression_12() + { + var src = """ +D d = [1, 2]; + +static class E +{ + extension(C c) + { + public void Add(int i) { System.Console.Write(i); } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} + +public class D : C { } +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_13() + { + var src = """ +C c = new C(); +D d = [.. c]; + +static class E +{ + extension(C c) + { + public System.Collections.Generic.IEnumerator GetEnumerator() + { + yield return 1; + yield return 2; + } + } +} + +public class C { } + +public class D : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; + public void Add(int i) { System.Console.Write(i); } +} +"""; + var comp = CreateCompilation(src); + CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + } + + [Fact] + public void CollectionExpression_14() + { + // Static Create extension methods does not count as a blessed Create method + string src = """ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +MyCollection c1 = []; +MyCollection c2 = [1]; + +[CollectionBuilder(typeof(MyCollectionBuilder), "Create")] +class MyCollection +{ + public IEnumerator GetEnumerator() => default; +} + +class MyCollectionBuilder +{ +} + +static class E +{ + extension(MyCollectionBuilder) + { + public static MyCollection Create(ReadOnlySpan items) => default; + } +} +"""; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (5,24): error CS9187: Could not find an accessible 'Create' method with the expected signature: a static method with a single parameter of type 'ReadOnlySpan' and return type 'MyCollection'. + // MyCollection c1 = []; + Diagnostic(ErrorCode.ERR_CollectionBuilderAttributeMethodNotFound, "[]").WithArguments("Create", "T", "MyCollection").WithLocation(5, 24), + // (6,24): error CS9187: Could not find an accessible 'Create' method with the expected signature: a static method with a single parameter of type 'ReadOnlySpan' and return type 'MyCollection'. + // MyCollection c2 = [1]; + Diagnostic(ErrorCode.ERR_CollectionBuilderAttributeMethodNotFound, "[1]").WithArguments("Create", "T", "MyCollection").WithLocation(6, 24)); + } + + [Fact] + public void CollectionExpression_15() + { + // extension countable property + var src = """ +C c = new C(); +int[] i = [.. c]; +System.Console.Write((i[0], i[1])); + +static class E +{ + extension(C c) + { + public int Length => throw null; + } +} + +public class C +{ + public System.Collections.Generic.IEnumerator GetEnumerator() + { + yield return 1; + yield return 2; + } +} +"""; + var comp = CreateCompilation(src); + var verifier = CompileAndVerify(comp, expectedOutput: "(1, 2)").VerifyDiagnostics(); + verifier.VerifyIL("", """ +{ + // Code size 82 (0x52) + .maxstack 3 + .locals init (int[] V_0, //i + System.Collections.Generic.List V_1, + System.Collections.Generic.IEnumerator V_2, + int V_3) + IL_0000: newobj "C..ctor()" + IL_0005: newobj "System.Collections.Generic.List..ctor()" + IL_000a: stloc.1 + IL_000b: callvirt "System.Collections.Generic.IEnumerator C.GetEnumerator()" + IL_0010: stloc.2 + .try + { + IL_0011: br.s IL_0021 + IL_0013: ldloc.2 + IL_0014: callvirt "int System.Collections.Generic.IEnumerator.Current.get" + IL_0019: stloc.3 + IL_001a: ldloc.1 + IL_001b: ldloc.3 + IL_001c: callvirt "void System.Collections.Generic.List.Add(int)" + IL_0021: ldloc.2 + IL_0022: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_0027: brtrue.s IL_0013 + IL_0029: leave.s IL_0035 + } + finally + { + IL_002b: ldloc.2 + IL_002c: brfalse.s IL_0034 + IL_002e: ldloc.2 + IL_002f: callvirt "void System.IDisposable.Dispose()" + IL_0034: endfinally + } + IL_0035: ldloc.1 + IL_0036: callvirt "int[] System.Collections.Generic.List.ToArray()" + IL_003b: stloc.0 + IL_003c: ldloc.0 + IL_003d: ldc.i4.0 + IL_003e: ldelem.i4 + IL_003f: ldloc.0 + IL_0040: ldc.i4.1 + IL_0041: ldelem.i4 + IL_0042: newobj "System.ValueTuple..ctor(int, int)" + IL_0047: box "System.ValueTuple" + IL_004c: call "void System.Console.Write(object)" + IL_0051: ret +} +"""); + + // non-extension countable property + src = """ +C c = new C(); +int[] i = [.. c]; +System.Console.Write((i[0], i[1])); + +public class C +{ + public System.Collections.Generic.IEnumerator GetEnumerator() + { + yield return 1; + yield return 2; + } + + public int Length => 2; +} +"""; + comp = CreateCompilation(src); + verifier = CompileAndVerify(comp, expectedOutput: "(1, 2)").VerifyDiagnostics(); + verifier.VerifyIL("", """ +{ + // Code size 88 (0x58) + .maxstack 3 + .locals init (int[] V_0, //i + int V_1, + int[] V_2, + System.Collections.Generic.IEnumerator V_3, + int V_4) + IL_0000: newobj "C..ctor()" + IL_0005: ldc.i4.0 + IL_0006: stloc.1 + IL_0007: dup + IL_0008: callvirt "int C.Length.get" + IL_000d: newarr "int" + IL_0012: stloc.2 + IL_0013: callvirt "System.Collections.Generic.IEnumerator C.GetEnumerator()" + IL_0018: stloc.3 + .try + { + IL_0019: br.s IL_002c + IL_001b: ldloc.3 + IL_001c: callvirt "int System.Collections.Generic.IEnumerator.Current.get" + IL_0021: stloc.s V_4 + IL_0023: ldloc.2 + IL_0024: ldloc.1 + IL_0025: ldloc.s V_4 + IL_0027: stelem.i4 + IL_0028: ldloc.1 + IL_0029: ldc.i4.1 + IL_002a: add + IL_002b: stloc.1 + IL_002c: ldloc.3 + IL_002d: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_0032: brtrue.s IL_001b + IL_0034: leave.s IL_0040 + } + finally + { + IL_0036: ldloc.3 + IL_0037: brfalse.s IL_003f + IL_0039: ldloc.3 + IL_003a: callvirt "void System.IDisposable.Dispose()" + IL_003f: endfinally + } + IL_0040: ldloc.2 + IL_0041: stloc.0 + IL_0042: ldloc.0 + IL_0043: ldc.i4.0 + IL_0044: ldelem.i4 + IL_0045: ldloc.0 + IL_0046: ldc.i4.1 + IL_0047: ldelem.i4 + IL_0048: newobj "System.ValueTuple..ctor(int, int)" + IL_004d: box "System.ValueTuple" + IL_0052: call "void System.Console.Write(object)" + IL_0057: ret +} +"""); + } + + [Fact] + public void ParamsCollection_01() + { + var src = """ +local([1, 2]); + +void local(params C c) +{ +} + +static class E +{ + extension(C c) + { + public void Add(int i) { } + } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (3,12): error CS9227: 'C' does not contain a definition for a suitable instance 'Add' method + // void local(params C c) + Diagnostic(ErrorCode.ERR_ParamsCollectionExtensionAddMethod, "params C c").WithArguments("C").WithLocation(3, 12)); + + src = """ +local([1, 2]); + +void local(params C c) +{ +} + +static class E +{ + public static void Add(this C c, int i) { } +} + +public class C : System.Collections.IEnumerable +{ + public System.Collections.IEnumerator GetEnumerator() => null; +} +"""; + comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (3,12): error CS9227: 'C' does not contain a definition for a suitable instance 'Add' method + // void local(params C c) + Diagnostic(ErrorCode.ERR_ParamsCollectionExtensionAddMethod, "params C c").WithArguments("C").WithLocation(3, 12)); + } }