diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 1c8f97db8f811..12a6810d08e28 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -2980,19 +2980,6 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy switch ((conv1.Kind, conv2.Kind)) { case (ConversionKind.ImplicitSpan, ConversionKind.ImplicitSpan): - // If the expression is of an array type, prefer ReadOnlySpan over Span (to avoid ArrayTypeMismatchExceptions). - if (node.Type is ArrayTypeSymbol) - { - if (t1.IsReadOnlySpan() && t2.IsSpan()) - { - return BetterResult.Left; - } - - if (t1.IsSpan() && t2.IsReadOnlySpan()) - { - return BetterResult.Right; - } - } break; case (_, ConversionKind.ImplicitSpan): return BetterResult.Right; @@ -3454,6 +3441,27 @@ private BetterResult BetterConversionTargetCore( return BetterResult.Neither; } + // SPEC: T₁ is System.ReadOnlySpan, T₂ is System.Span, and an identity conversion from E₁ to E₂ exists + if (Compilation.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan)) + { + if (isBetterSpanConversionTarget(type1, type2)) + { + return BetterResult.Left; + } + else if (isBetterSpanConversionTarget(type2, type1)) + { + return BetterResult.Right; + } + // The next case (a type is a better target if an implicit conversion from it to the other type exists) + // does not apply to span conversions except the covariant ReadOnlySpan->ReadOnlySpan conversion. + else if ((type1.IsSpan() || type1.IsReadOnlySpan()) && + (type2.IsSpan() || type2.IsReadOnlySpan()) && + !(type1.IsReadOnlySpan() && type2.IsReadOnlySpan())) + { + return BetterResult.Neither; + } + } + // Given two different types T1 and T2, T1 is a better conversion target than T2 if no implicit conversion from T2 to T1 exists, // and at least one of the following holds: bool type1ToType2 = Conversions.ClassifyImplicitConversionFromType(type1, type2, ref useSiteInfo).IsImplicit; @@ -3592,6 +3600,22 @@ private BetterResult BetterConversionTargetCore( } return BetterResult.Neither; + + static bool isBetterSpanConversionTarget(TypeSymbol type1, TypeSymbol type2) + { + // SPEC: T₁ is System.ReadOnlySpan, T₂ is System.Span, and an identity conversion from E₁ to E₂ exists + if (type1.IsReadOnlySpan() && type2.IsSpan()) + { + var type1Element = ((NamedTypeSymbol)type1).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + var type2Element = ((NamedTypeSymbol)type2).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + if (Conversions.HasIdentityConversion(type1Element, type2Element)) + { + return true; + } + } + + return false; + } } private bool IsMethodGroupConversionIncompatibleWithDelegate(BoundMethodGroup node, NamedTypeSymbol delegateType, Conversion conv) diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 34ca8063a319b..f4004a3170e99 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -8043,15 +8043,8 @@ static class C [Fact] public void OverloadResolution_SpanVsReadOnlySpan_04() { - var source = """ + var source1 = """ using System; - - C.M1(new object[0]); - C.M1(new string[0]); - - C.M2(new object[0]); - C.M2(new string[0]); - static class C { public static void M1(Span arg) => Console.Write(1); @@ -8061,8 +8054,308 @@ static class C public static void M2(ReadOnlySpan arg) => Console.Write(2); } """; - var comp = CreateCompilationWithSpanAndMemoryExtensions(source); - CompileAndVerify(comp, expectedOutput: "2212").VerifyDiagnostics(); + + { + var source2 = """ + C.M1(new object[0]); + C.M2(new object[0]); + """; + + var expectedOutput = "21"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source2], parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source2], parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source2]); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + { + var source3 = """ + C.M1(new string[0]); + """; + + var expectedDiagnostics = new[] + { + // (1,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M1(Span)' and 'C.M1(ReadOnlySpan)' + // C.M1(new string[0]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("C.M1(System.Span)", "C.M1(System.ReadOnlySpan)").WithLocation(1, 3) + }; + + CreateCompilationWithSpanAndMemoryExtensions([source1, source3], + parseOptions: TestOptions.Regular13).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions([source1, source3], + parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions([source1, source3]).VerifyDiagnostics(expectedDiagnostics); + } + + { + var source4 = """ + C.M2(new string[0]); + """; + + CreateCompilationWithSpanAndMemoryExtensions([source1, source4], + parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (1,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M2(Span)' and 'C.M2(ReadOnlySpan)' + // C.M2(new string[0]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments("C.M2(System.Span)", "C.M2(System.ReadOnlySpan)").WithLocation(1, 3)); + + var expectedOutput = "2"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source4], parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source4]); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_05() + { + var source = """ + using System; + + C.M(new D()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + public static implicit operator ReadOnlySpan(D d) => default; + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.FailsILVerify).VerifyDiagnostics(); + + var expectedOutput = "2"; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_06() + { + var source = """ + using System; + + C.M(new D()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + public static implicit operator ReadOnlySpan(D d) => default; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (3,5): error CS1503: Argument 1: cannot convert from 'D' to 'System.Span' + // C.M(new D()); + Diagnostic(ErrorCode.ERR_BadArgType, "new D()").WithArguments("1", "D", "System.Span").WithLocation(3, 5)); + + var expectedDiagnostics = new[] + { + // (3,5): error CS0457: Ambiguous user defined conversions 'D.implicit operator Span(D)' and 'D.implicit operator ReadOnlySpan(D)' when converting from 'D' to 'ReadOnlySpan' + // C.M(new D()); + Diagnostic(ErrorCode.ERR_AmbigUDConv, "new D()").WithArguments("D.implicit operator System.Span(D)", "D.implicit operator System.ReadOnlySpan(D)", "D", "System.ReadOnlySpan").WithLocation(3, 5) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, MemberData(nameof(LangVersions))] + public void OverloadResolution_SpanVsReadOnlySpan_07(LanguageVersion langVersion) + { + var source = """ + using System; + + C.M(new D()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + public static implicit operator ReadOnlySpan(D d) => default; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (3,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(Span)' and 'C.M(ReadOnlySpan)' + // C.M(new D()); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(System.Span)", "C.M(System.ReadOnlySpan)").WithLocation(3, 3)); + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_08() + { + var source = """ + using System; + + (int, int)[] t = [(1, 2)]; + + C.M1(t); + C.M2(t); + + static class C + { + public static void M1(Span<(int X, int Y)> arg) => Console.Write(1); + public static void M1(ReadOnlySpan<(int, int)> arg) => Console.Write(2); + + public static void M2(Span<(int X, int Y)> arg) => Console.Write(1); + public static void M2(ReadOnlySpan<(int A, int B)> arg) => Console.Write(2); + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: "11").VerifyDiagnostics(); + + var expectedOutput = "22"; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_09() + { + var source = """ + using System; + + object[] a = []; + + C.M1(a); + C.M2(a); + + static class C + { + public static void M1(Span arg) => Console.Write(1); + public static void M1(ReadOnlySpan arg) => Console.Write(2); + + public static void M2(Span arg) => Console.Write(1); + public static void M2(ReadOnlySpan arg) => Console.Write(2); + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: "11").VerifyDiagnostics(); + + var expectedOutput = "22"; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_10() + { + var source = """ + using System; + + C.M(new D()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + public static implicit operator ReadOnlySpan(D d) => default; + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.FailsILVerify).VerifyDiagnostics(); + + var expectedOutput = "2"; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + } + + [Theory, MemberData(nameof(LangVersions))] + public void OverloadResolution_SpanVsReadOnlySpan_11(LanguageVersion langVersion) + { + var source = """ + using System; + + C.M(new D()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + public static implicit operator ReadOnlySpan(D d) => default; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (3,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(Span)' and 'C.M(ReadOnlySpan)' + // C.M(new D()); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(System.Span)", "C.M(System.ReadOnlySpan)").WithLocation(3, 3)); + } + + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_12() + { + var source = """ + using System; + + C.M(() => new object[0]); + + delegate Span D1(); + delegate ReadOnlySpan D2(); + + static class C + { + public static void M(D1 arg) => Console.Write(1); + public static void M(D2 arg) => Console.Write(2); + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + CompileAndVerify(comp, expectedOutput: "1", verify: Verification.FailsILVerify).VerifyDiagnostics(); + + var expectedOutput = "2"; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); } [Fact] @@ -8179,15 +8472,8 @@ static class C [Fact] public void OverloadResolution_SpanVsReadOnlySpan_ExtensionMethodReceiver_05() { - var source = """ + var source1 = """ using System; - - (new object[0]).M1(); - (new string[0]).M1(); - - (new object[0]).M2(); - (new string[0]).M2(); - static class E { public static void M1(this Span arg) => Console.Write(1); @@ -8197,8 +8483,22 @@ static class E public static void M2(this ReadOnlySpan arg) => Console.Write(2); } """; - var comp = CreateCompilationWithSpanAndMemoryExtensions(source); - CompileAndVerify(comp, expectedOutput: "2212").VerifyDiagnostics(); + + var source2 = """ + (new object[0]).M1(); + (new object[0]).M2(); + (new string[0]).M2(); + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions([source1, source2]); + CompileAndVerify(comp, expectedOutput: "212").VerifyDiagnostics(); + + var source3 = """ + (new string[0]).M1(); + """; + CreateCompilationWithSpanAndMemoryExtensions([source1, source3]).VerifyDiagnostics( + // (1,17): error CS0121: The call is ambiguous between the following methods or properties: 'E.M1(Span)' and 'E.M1(ReadOnlySpan)' + // (new string[0]).M1(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("E.M1(System.Span)", "E.M1(System.ReadOnlySpan)").WithLocation(1, 17)); } [Fact] @@ -8403,6 +8703,31 @@ class C CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } + [Theory, MemberData(nameof(LangVersions))] + public void OverloadResolution_SpanVsUserDefined_03(LanguageVersion langVersion) + { + var source = """ + using System; + + C.M(new E()); + + static class C + { + public static void M(Span arg) => Console.Write(1); + public static void M(D arg) => Console.Write(2); + } + + class D + { + public static implicit operator Span(D d) => default; + } + + class E : D; + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + CompileAndVerify(comp, expectedOutput: "2", verify: Verification.FailsILVerify).VerifyDiagnostics(); + } + [Fact] public void OverloadResolution_ReadOnlySpanVsArrayVsSpan() {