diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index db64b457fd9f3..f90fd1121a409 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -2531,29 +2531,19 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy if (!conv2.IsConditionalExpression && conv1.IsConditionalExpression) return BetterResult.Right; - // C1 and C2 are collection expression conversions and - // T1 is a span_type with iteration type E1, and - // T2 is an array_or_array_interface_or_string_type with iteration type E2, and - // - E1 is implicitly convertible to E2 + // - E is a collection expression and one of the following holds: ... if (conv1.Kind == ConversionKind.CollectionExpression && conv2.Kind == ConversionKind.CollectionExpression) { - TypeSymbol elementType; - TypeWithAnnotations typeArg; - if ((conv1.GetCollectionExpressionTypeKind(out elementType) is CollectionExpressionTypeKind.Span or CollectionExpressionTypeKind.ReadOnlySpan) && - IsSZArrayOrArrayInterfaceOrString(t2, out typeArg)) + if (IsBetterCollectionExpressionConversion(t1, conv1, t2, conv2, ref useSiteInfo)) { - return Conversions.ClassifyImplicitConversionFromType(elementType, typeArg.Type, ref useSiteInfo).IsImplicit ? - BetterResult.Left : - BetterResult.Neither; + return BetterResult.Left; } - if ((conv2.GetCollectionExpressionTypeKind(out elementType) is CollectionExpressionTypeKind.Span or CollectionExpressionTypeKind.ReadOnlySpan) && - IsSZArrayOrArrayInterfaceOrString(t1, out typeArg)) + if (IsBetterCollectionExpressionConversion(t2, conv2, t1, conv1, ref useSiteInfo)) { - return Conversions.ClassifyImplicitConversionFromType(elementType, typeArg.Type, ref useSiteInfo).IsImplicit ? - BetterResult.Right : - BetterResult.Neither; + return BetterResult.Right; } + return BetterResult.Neither; } // - T1 is a better conversion target than T2 and either C1 and C2 are both conditional expression @@ -2561,27 +2551,67 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy return BetterConversionTarget(node, t1, conv1, t2, conv2, ref useSiteInfo, out okToDowngradeToNeither); } - private bool IsSZArrayOrArrayInterfaceOrString(TypeSymbol type, out TypeWithAnnotations elementType) + // Implements the rules for + // - E is a collection expression and one of the following holds: ... + private bool IsBetterCollectionExpressionConversion(TypeSymbol t1, Conversion conv1, TypeSymbol t2, Conversion conv2, ref CompoundUseSiteInfo useSiteInfo) + { + TypeSymbol elementType1; + var kind1 = conv1.GetCollectionExpressionTypeKind(out elementType1); + TypeSymbol elementType2; + var kind2 = conv2.GetCollectionExpressionTypeKind(out elementType2); + + // - T1 is System.ReadOnlySpan, and T2 is System.Span, and an implicit conversion exists from E1 to E2 + if (kind1 is CollectionExpressionTypeKind.ReadOnlySpan && + kind2 is CollectionExpressionTypeKind.Span && + hasImplicitConversion(elementType1, elementType2, ref useSiteInfo)) + { + return true; + } + + // - T1 is System.ReadOnlySpan or System.Span, and T2 is an array_or_array_interface_or_string_type + // with iteration type E2, and an implicit conversion exists from E1 to E2 + if (kind1 is CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span && + IsSZArrayOrArrayInterfaceOrString(t2, out elementType2) && + hasImplicitConversion(elementType1, elementType2, ref useSiteInfo)) + { + return true; + } + + // - T1 is not a span_type, and T2 is not a span_type, and an implicit conversion exists from T1 to T2 + if (kind1 is not (CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span) && + kind2 is not (CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span) && + hasImplicitConversion(t1, t2, ref useSiteInfo)) + { + return true; + } + + return false; + + bool hasImplicitConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) => + Conversions.ClassifyImplicitConversionFromType(source, destination, ref useSiteInfo).IsImplicit; + } + + private bool IsSZArrayOrArrayInterfaceOrString(TypeSymbol type, out TypeSymbol elementType) { if (type.SpecialType == SpecialType.System_String) { - elementType = TypeWithAnnotations.Create(Compilation.GetSpecialType(SpecialType.System_Char)); + elementType = Compilation.GetSpecialType(SpecialType.System_Char); return true; } if (type is ArrayTypeSymbol { IsSZArray: true } arrayType) { - elementType = arrayType.ElementTypeWithAnnotations; + elementType = arrayType.ElementType; return true; } if (type.IsArrayInterface(out TypeWithAnnotations typeArg)) { - elementType = typeArg; + elementType = typeArg.Type; return true; } - elementType = default; + elementType = null; return false; } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 43ca663e35c12..38675cbf84d59 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -1241,6 +1241,47 @@ static class NonGenericClassCollectionBuilder } """; + private const string example_GenericClassesWithConversion = """ + using System; + using System.Collections; + using System.Collections.Generic; + public sealed class MyCollectionA : IEnumerable + { + public void Add(T t) { } + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + } + public sealed class MyCollectionB : IEnumerable + { + public void Add(T t) { } + IEnumerator IEnumerable.GetEnumerator() => null; + IEnumerator IEnumerable.GetEnumerator() => null; + public static implicit operator MyCollectionA(MyCollectionB b) => default; + } + """; + + // Ref struct collection, with an implicit conversion from array. + private const string example_RefStructConvertibleFromArray = """ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + [CollectionBuilder(typeof(RefStructConvertibleFromArrayBuilder), nameof(RefStructConvertibleFromArrayBuilder.Create))] + public ref struct RefStructConvertibleFromArray + { + private readonly T[] _array; + public RefStructConvertibleFromArray(T[] array) { _array = array; } + public IEnumerator GetEnumerator() => new List(_array).GetEnumerator(); + public static implicit operator RefStructConvertibleFromArray(T[] array) => new(array); + } + public static class RefStructConvertibleFromArrayBuilder + { + public static RefStructConvertibleFromArray Create(scoped ReadOnlySpan items) + { + return new RefStructConvertibleFromArray(items.ToArray()); + } + } + """; + [Theory] [InlineData("System.Span", "T[]", "System.Span")] [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Span")] @@ -1255,7 +1296,7 @@ static class NonGenericClassCollectionBuilder [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.ReadOnlySpan")] [InlineData("System.Span", "System.Collections.Generic.HashSet", null)] // rule requires array or array interface - [InlineData("System.Span", "System.ReadOnlySpan", null)] // rule requires array or array interface + [InlineData("System.Span", "System.ReadOnlySpan", null)] // cannot convert from object to int [InlineData("RefStructCollection", "T[]", null, new[] { example_RefStructCollection })] // rule requires span [InlineData("RefStructCollection", "RefStructCollection", null, new[] { example_RefStructCollection })] // rule requires span [InlineData("RefStructCollection", "GenericClassCollection", null, new[] { example_RefStructCollection, example_GenericClassCollection })] // rule requires span @@ -1270,12 +1311,107 @@ static class NonGenericClassCollectionBuilder [InlineData("System.ReadOnlySpan", "long[]", null)] // cannot convert object to long [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "string[]", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] // implicit conversion from Span to ReadOnlySpan - [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] - [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] // cannot convert between ReadOnlySpan and ReadOnlySpan - [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] // cannot convert between ReadOnlySpan and ReadOnlySpan - [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] // cannot convert between ReadOnlySpan and ReadOnlySpan [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", null)] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] + [InlineData("System.Span", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Span", null)] + [InlineData("System.Span", "System.Span", null)] + [InlineData("System.Span", "System.Span", "System.Span")] + [InlineData("T[]", "int[]", "System.Int32[]")] + [InlineData("T[]", "object[]", null)] + [InlineData("T[]", "int?[]", null)] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", null)] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", null)] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.IReadOnlyCollection", null)] + [InlineData("MyCollectionA", "MyCollectionB", "MyCollectionB", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", "MyCollectionB", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", null, new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", null, new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionB", "MyCollectionB", null, new[] { example_GenericClassesWithConversion })] + [InlineData("RefStructConvertibleFromArray", "T[]", "System.Int32[]", new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "int[]", "System.Int32[]", new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "T[]", null, new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "object[]", null, new[] { example_RefStructConvertibleFromArray })] + public void BetterConversionFromExpression_01A(string type1, string type2, string expectedType, string[] additionalSources = null) + { + string source = $$""" + using System; + class Program + { + {{generateMethod("F1", type1)}} + {{generateMethod("F1", type2)}} + {{generateMethod("F2", type2)}} + {{generateMethod("F2", type1)}} + static void Main() + { + var x = F1([1, 2, 3]); + Console.WriteLine(x.GetTypeName()); + var y = F2([4, 5]); + Console.WriteLine(y.GetTypeName()); + } + } + """; + var comp = CreateCompilation( + getSources(source, additionalSources), + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + if (expectedType is { }) + { + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput($""" + {expectedType} + {expectedType} + """)); + } + else + { + comp.VerifyEmitDiagnostics( + // 0.cs(10,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(ReadOnlySpan)' and 'Program.F1(ReadOnlySpan)' + // var x = F1([1, 2, 3]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(generateMethodSignature("F1", type1), generateMethodSignature("F1", type2)).WithLocation(10, 17), + // 0.cs(12,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(ReadOnlySpan)' and 'Program.F2(ReadOnlySpan)' + // var y = F2([4, 5]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(generateMethodSignature("F2", type2), generateMethodSignature("F2", type1)).WithLocation(12, 17)); + } + + static string getTypeParameters(string type) => + type.Contains("T[]") || type.Contains("") ? "" : ""; + + static string generateMethod(string methodName, string parameterType) => + $"static Type {methodName}{getTypeParameters(parameterType)}({parameterType} value) => typeof({parameterType});"; + + static string generateMethodSignature(string methodName, string parameterType) => + $"Program.{methodName}{getTypeParameters(parameterType)}({parameterType})"; + + static string[] getSources(string source, string[] additionalSources) + { + var builder = ArrayBuilder.GetInstance(); + builder.Add(source); + builder.Add(s_collectionExtensions); + if (additionalSources is { }) builder.AddRange(additionalSources); + return builder.ToArrayAndFree(); + } + } + + [Theory] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Span", null)] // cannot convert int? to int + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] + [InlineData("System.Span", "System.Span", null)] + [InlineData("System.Span", "System.Span", null)] + [InlineData("System.Span", "System.Span", null)] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null)] [InlineData("System.Span", "int?[]", "System.Span")] [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Span")] [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Span")] @@ -1303,7 +1439,7 @@ static class NonGenericClassCollectionBuilder [InlineData("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.List")] [InlineData("int[]", "object[]", null)] // rule requires span [InlineData("int[]", "System.Collections.Generic.IReadOnlyList", null)] // rule requires span - public void BetterConversionFromExpression_01(string type1, string type2, string expectedType, string[] additionalSources = null) + public void BetterConversionFromExpression_01B(string type1, string type2, string expectedType) { string source = $$""" using System; @@ -1315,20 +1451,26 @@ class Program {{generateMethod("F2", type1)}} static void Main() { - var x = F1([1, 2, 3]); - Console.WriteLine(x.GetTypeName()); - var y = F2([4, 5]); - Console.WriteLine(y.GetTypeName()); + var a = F1([]); + Console.WriteLine(a.GetTypeName()); + var b = F2([]); + Console.WriteLine(b.GetTypeName()); + var c = F1([1, 2, 3]); + Console.WriteLine(c.GetTypeName()); + var d = F2([4, 5]); + Console.WriteLine(d.GetTypeName()); } } """; var comp = CreateCompilation( - getSources(source, additionalSources), + new[] { source, s_collectionExtensions }, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); if (expectedType is { }) { CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput($""" + {expectedType} + {expectedType} {expectedType} {expectedType} """)); @@ -1336,31 +1478,25 @@ static void Main() else { comp.VerifyEmitDiagnostics( - // 0.cs(10,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(ReadOnlySpan)' and 'Program.F1(ReadOnlySpan)' - // var x = F1([1, 2, 3]); + // 0.cs(10,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(int[])' and 'Program.F1(object[])' + // var a = F1([]); Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(generateMethodSignature("F1", type1), generateMethodSignature("F1", type2)).WithLocation(10, 17), - // 0.cs(12,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(ReadOnlySpan)' and 'Program.F2(ReadOnlySpan)' - // var y = F2([4, 5]); - Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(generateMethodSignature("F2", type2), generateMethodSignature("F2", type1)).WithLocation(12, 17)); + // 0.cs(12,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(object[])' and 'Program.F2(int[])' + // var b = F2([]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(generateMethodSignature("F2", type2), generateMethodSignature("F2", type1)).WithLocation(12, 17), + // 0.cs(14,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(int[])' and 'Program.F1(object[])' + // var c = F1([1, 2, 3]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(generateMethodSignature("F1", type1), generateMethodSignature("F1", type2)).WithLocation(14, 17), + // 0.cs(16,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(object[])' and 'Program.F2(int[])' + // var d = F2([4, 5]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(generateMethodSignature("F2", type2), generateMethodSignature("F2", type1)).WithLocation(16, 17)); } - static string getTypeParameters(string type) => - type.Contains("T[]") || type.Contains("") ? "" : ""; - static string generateMethod(string methodName, string parameterType) => - $"static Type {methodName}{getTypeParameters(parameterType)}({parameterType} value) => typeof({parameterType});"; + $"static Type {methodName}({parameterType} value) => typeof({parameterType});"; static string generateMethodSignature(string methodName, string parameterType) => - $"Program.{methodName}{getTypeParameters(parameterType)}({parameterType})"; - - static string[] getSources(string source, string[] additionalSources) - { - var builder = ArrayBuilder.GetInstance(); - builder.Add(source); - builder.Add(s_collectionExtensions); - if (additionalSources is { }) builder.AddRange(additionalSources); - return builder.ToArrayAndFree(); - } + $"Program.{methodName}({parameterType})"; } [Fact] @@ -1604,7 +1740,7 @@ static void Main() } """; CompileAndVerify( - new[] { source, CollectionBuilderAttributeDefinition }, + source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(""" @@ -1616,6 +1752,75 @@ static void Main() """)); } + [Fact] + public void BetterConversionFromExpression_07() + { + string source = """ + using System; + class Program + { + static void F1(ReadOnlySpan value) { } + static void F1(ReadOnlySpan value) { } + static void F2(Span value) { } + static void F2(Span value) { } + static void Main() + { + F1([1, 2, 3]); + F2(["a", "b"]); + } + } + """; + var comp = CreateCompilation( + source, + targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (10,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(ReadOnlySpan)' and 'Program.F1(ReadOnlySpan)' + // F1([1, 2, 3]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(System.ReadOnlySpan)", "Program.F1(System.ReadOnlySpan)").WithLocation(10, 9), + // (11,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(Span)' and 'Program.F2(Span)' + // F2(["a", "b"]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(System.Span)", "Program.F2(System.Span)").WithLocation(11, 9)); + } + + [Fact] + public void BetterConversionFromExpression_08A() + { + string source = """ + class Program + { + static void F1(int[] value) { } + static void F1(object[] value) { } + static void Main() + { + F1([1, 2, 3]); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (7,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(int[])' and 'Program.F1(object[])' + // F1([1, 2, 3]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(int[])", "Program.F1(object[])").WithLocation(7, 9)); + } + + [Fact] + public void BetterConversionFromExpression_08B() + { + string source = """ + using System; + class Program + { + static void F2(string[] value) { Console.WriteLine("string[]"); } + static void F2(object[] value) { Console.WriteLine("object[]"); } + static void Main() + { + F2(["a", "b"]); + } + } + """; + CompileAndVerify(source, expectedOutput: "string[]"); + } + [Theory] [InlineData("System.ReadOnlySpan")] [InlineData("System.Span")] @@ -1778,6 +1983,39 @@ static void Main() """)); } + [Fact] + public void BetterConversionFromExpression_String_05() + { + string source = $$""" + using System; + using System.Collections.Generic; + using static System.Console; + + class Program + { + static void F(IEnumerable value) { WriteLine("F(IEnumerable)"); } + static void F(string value) { WriteLine("F(string)"); } + + static void Main() + { + F([]); + F(['a']); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (12,11): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // F([]); + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "[]").WithArguments("string", "0").WithLocation(12, 11), + // (13,11): error CS1729: 'string' does not contain a constructor that takes 0 arguments + // F(['a']); + Diagnostic(ErrorCode.ERR_BadCtorArgCount, "['a']").WithArguments("string", "0").WithLocation(13, 11), + // (13,12): error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) + // F(['a']); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "'a'").WithArguments("string", "Add").WithLocation(13, 12)); + } + [Fact] public void BestCommonType_01() {