diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 4037d257257a6..e1aabeff24c99 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -29,6 +29,7 @@ efforts behind them. | [Overload Resolution Priority](https://github.com/dotnet/csharplang/issues/7706) | main | [Merged to 17.12p1](https://github.com/dotnet/roslyn/issues/74131) | [333fred](https://github.com/333fred) | [jcouv](https://github.com/jcouv), [cston](https://github.com/cston) | Not yet assigned | [333fred](https://github.com/333fred) | | [Partial properties](https://github.com/dotnet/csharplang/issues/6420) | [partial-properties](https://github.com/dotnet/roslyn/tree/features/partial-properties) | [Merged into 17.11p3](https://github.com/dotnet/roslyn/issues/73090) | [RikkiGibson](https://github.com/RikkiGibson) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [Cosifne](https://github.com/Cosifne) | [333fred](https://github.com/333fred), [RikkiGibson](https://github.com/RikkiGibson) | | [Ref Struct Interfaces](https://github.com/dotnet/csharplang/issues/7608) | [RefStructInterfaces](https://github.com/dotnet/roslyn/tree/features/RefStructInterfaces) | [Merged into 17.11p2](https://github.com/dotnet/roslyn/issues/72124) | [AlekseyTs](https://github.com/AlekseyTs) | [cston](https://github.com/cston), [jjonescz](https://github.com/jjonescz) | [ToddGrun](https://github.com/ToddGrun) | [agocke](https://github.com/agocke), [jaredpar](https://github.com/jaredpar) | +| [Collection expression better conversion from expression](https://github.com/dotnet/csharplang/issues/8374) | main | [Merged into 17.12p3](https://github.com/dotnet/roslyn/pull/74993) | [333fred](https://github.com/333fred) | [cston](https://github.com/cston), [AlekseyTs](https://github.com/AlekseyTs) | (no IDE impact) | [333fred](https://github.com/333fred), [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | # C# 12.0 diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md index 698ae888af3c0..34688d39142e5 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md @@ -44,3 +44,52 @@ unsafe class C // unsafe context ``` You can work around the break simply by adding the `unsafe` modifier to the local function. + +## Collection expression breaking changes with overload resolution in C# 13 and newer + +***Introduced in Visual Studio 2022 Version 17.12 and newer when using C# 13+*** + +There are a few changes in collection expression binding in C# 13. Most of these are turning ambiguities into successful compilations, +but a couple are breaking changes that either result in a new compilation error, or are a behavior breaking change. They are detailed +below. + +### Empty collection expressions no longer use whether an API is a span to tiebreak on overloads + +When an empty collection expression is provided to an overloaded method, and there isn't a clear element type, we no longer use whether +an API takes a `ReadOnlySpan` or a `Span` to decide whether to prefer that API. For example: + +```cs +class C +{ + static void M(ReadOnlySpan ros) {} + static void M(Span s) {} + + static void Main() + { + M([]); // C.M(ReadOnlySpan) in C# 12, error in C# 13. + } +} +``` + +### Exact element type is preferred over all else + +In C# 13, we prefer an exact element type match, looking at conversions from expressions. This can result in a behavior change when involving +constants: + +```cs +class C +{ + static void M1(ReadOnlySpan ros) {} + static void M1(Span s) {} + + static void M2(ReadOnlySpan ros) {} + static void M2(Span ros) {} + + static void Main() + { + M1([1]); // C.M(ReadOnlySpan) in C# 12, C.M(Span) in C# 13 + + M2([$"{1}"]); // C.M(ReadOnlySpan) in C# 12, C.M(Span) in C# 13 + } +} +``` diff --git a/docs/contributing/Compiler Test Plan.md b/docs/contributing/Compiler Test Plan.md index f30047e69b4a5..72e8afa3088a1 100644 --- a/docs/contributing/Compiler Test Plan.md +++ b/docs/contributing/Compiler Test Plan.md @@ -116,6 +116,7 @@ This document provides guidance for thinking about language interactions and tes - extension based Dispose, DisposeAsync, GetEnumerator, GetAsyncEnumerator, Deconstruct, GetAwaiter etc. - UTF8 String Literals (string literals with 'u8' or 'U8' type suffix). - Inline array element access and slicing. +- Collection expressions and spread elements # Misc - reserved keywords (sometimes contextual) @@ -345,6 +346,7 @@ __makeref( x ) - Function type (in type inference comparing function types of lambdas or method groups) - UTF8 String Literal (string constant value to ```byte[]```, ```Span```, or ```ReadOnlySpan``` types) - Inline arrays (conversions to Span and ReadOnlySpan) +- Collection expression conversions ## Types diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index a103607ba37cd..ad118c7a047fa 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -2416,13 +2416,10 @@ private BetterResult BetterFunctionMember( if (!Conversions.HasIdentityConversion(t1, t2)) { - if (IsBetterParamsCollectionType(t1, t2, ref useSiteInfo)) + var betterResult = BetterParamsCollectionType(t1, t2, ref useSiteInfo); + if (betterResult != BetterResult.Neither) { - return BetterResult.Left; - } - if (IsBetterParamsCollectionType(t2, t1, ref useSiteInfo)) - { - return BetterResult.Right; + return betterResult; } } } @@ -2850,35 +2847,157 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy if (conv1.Kind == ConversionKind.CollectionExpression && conv2.Kind == ConversionKind.CollectionExpression) { - if (IsBetterCollectionExpressionConversion(t1, conv1, t2, conv2, ref useSiteInfo)) + return BetterCollectionExpressionConversion((BoundUnconvertedCollectionExpression)node, t1, conv1, t2, conv2, ref useSiteInfo); + } + + // - T1 is a better conversion target than T2 and either C1 and C2 are both conditional expression + // conversions or neither is a conditional expression conversion. + return BetterConversionTarget(node, t1, conv1, t2, conv2, ref useSiteInfo, out okToDowngradeToNeither); + } + + private BetterResult BetterCollectionExpressionConversion( + BoundUnconvertedCollectionExpression collectionExpression, + TypeSymbol t1, Conversion conv1, + TypeSymbol t2, Conversion conv2, + ref CompoundUseSiteInfo useSiteInfo) + { + var kind1 = conv1.GetCollectionExpressionTypeKind(out TypeSymbol elementType1, out _, out _); + var kind2 = conv2.GetCollectionExpressionTypeKind(out TypeSymbol elementType2, out _, out _); + + if (Compilation.LanguageVersion < LanguageVersion.CSharp13) + { + if (IsBetterCollectionExpressionConversion_CSharp12(t1, kind1, elementType1, t2, kind2, elementType2, ref useSiteInfo)) { return BetterResult.Left; } - if (IsBetterCollectionExpressionConversion(t2, conv2, t1, conv1, ref useSiteInfo)) + if (IsBetterCollectionExpressionConversion_CSharp12(t2, kind2, elementType2, t1, kind1, elementType1, ref useSiteInfo)) { return BetterResult.Right; } + return BetterResult.Neither; } - - // - T1 is a better conversion target than T2 and either C1 and C2 are both conditional expression - // conversions or neither is a conditional expression conversion. - return BetterConversionTarget(node, t1, conv1, t2, conv2, ref useSiteInfo, out okToDowngradeToNeither); + else + { + return BetterCollectionExpressionConversion( + collectionExpression.Elements, + t1, kind1, elementType1, conv1.UnderlyingConversions, + t2, kind2, elementType2, conv2.UnderlyingConversions, + ref useSiteInfo); + } } // 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) + private BetterResult BetterCollectionExpressionConversion( + ImmutableArray collectionExpressionElements, + TypeSymbol t1, CollectionExpressionTypeKind kind1, TypeSymbol elementType1, ImmutableArray underlyingElementConversions1, + TypeSymbol t2, CollectionExpressionTypeKind kind2, TypeSymbol elementType2, ImmutableArray underlyingElementConversions2, + ref CompoundUseSiteInfo useSiteInfo) { - TypeSymbol elementType1; - var kind1 = conv1.GetCollectionExpressionTypeKind(out elementType1, out _, out _); - TypeSymbol elementType2; - var kind2 = conv2.GetCollectionExpressionTypeKind(out elementType2, out _, out _); + // Given: + // - `E` is a collection expression with element expressions `[EL₁, EL₂, ..., ELₙ]` + // - `T₁` and `T₂` are collection types + // - `E₁` is the element type of `T₁` + // - `E₂` is the element type of `T₂` + // - `CE₁ᵢ` are the series of conversions from `ELᵢ` to `E₁` + // - `CE₂ᵢ` are the series of conversions from `ELᵢ` to `E₂` + + var t1IsSpanType = kind1 is CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span; + var t2IsSpanType = kind2 is CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span; + + // `C₁` is a ***better collection conversion from expression*** than `C₂` if: + // - Both T₁ and T₂ are not *span types*, and `T₁` is implicitly convertible to `T₂`, and `T₂` is not implicitly convertible to `T₁`, or + if (!t1IsSpanType && !t2IsSpanType) + { + var t1IsConvertibleToT2 = Conversions.ClassifyImplicitConversionFromType(t1, t2, ref useSiteInfo).IsImplicit; + var t2IsConvertibleToT1 = Conversions.ClassifyImplicitConversionFromType(t2, t1, ref useSiteInfo).IsImplicit; + + switch (t1IsConvertibleToT2, t2IsConvertibleToT1) + { + case (true, false): + return BetterResult.Left; + case (false, true): + return BetterResult.Right; + } + } + + // - `E₁` does not have an identity conversion to `E₂`, and the element conversions to `E₁` are better than the element conversions to `E₂`, or + // - `E₁` has an identity conversion to `E₂`, and one of the following holds: + + // `E₁` is compared to `E₂` as follows: + // If there is an identity conversion from `E₁` to `E₂`, then the element conversions are as good as each other. Otherwise, the element conversions + // to `E₁` are better than the element conversions to `E₂` if: + // - For every `ELᵢ`, `CE₁ᵢ` is at least as good as `CE₂ᵢ`, and + // - There is at least one i where `CE₁ᵢ` is better than `CE₂ᵢ` + // Otherwise, neither set of element conversions is better than the other, and they are also not as good as each other. + // Conversion comparisons are made using better conversion from expression if `ELᵢ` is not a spread element. If `ELᵢ` is a spread element, we use better conversion from the element type of the spread collection to `E₁` or `E₂`, respectively. + + if (!Conversions.HasIdentityConversion(elementType1, elementType2)) + { + var betterResult = BetterResult.Neither; + Debug.Assert(underlyingElementConversions1.Length == underlyingElementConversions2.Length && underlyingElementConversions1.Length == collectionExpressionElements.Length); + + for (int i = 0; i < underlyingElementConversions1.Length; i++) + { + // Conversion comparisons are made using better conversion from expression if `ELᵢ` is not a spread element. If `ELᵢ` is a spread element, + // we use better conversion from the element type of the spread collection to `E₁` or `E₂`, respectively. + var element = collectionExpressionElements[i]; + var conversionToE1 = underlyingElementConversions1[i]; + var conversionToE2 = underlyingElementConversions2[i]; - return IsBetterCollectionExpressionConversion(t1, kind1, elementType1, t2, kind2, elementType2, ref useSiteInfo); + BetterResult elementBetterResult; + if (element is BoundCollectionExpressionSpreadElement spread) + { + elementBetterResult = BetterConversionTarget(spread, elementType1, conversionToE1, elementType2, conversionToE2, ref useSiteInfo, okToDowngradeToNeither: out _); + } + else + { + elementBetterResult = BetterConversionFromExpression((BoundExpression)element, elementType1, conversionToE1, elementType2, conversionToE2, ref useSiteInfo, okToDowngradeToNeither: out _); + } + + if (elementBetterResult == BetterResult.Neither) + { + continue; + } + + if (betterResult != BetterResult.Neither) + { + if (betterResult != elementBetterResult) + { + return BetterResult.Neither; + } + } + else + { + betterResult = elementBetterResult; + } + } + + return betterResult; + } + + // - `T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, or + // - `T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *element type* `E₂` + + if (t1IsSpanType || t2IsSpanType) + { + switch ((kind1, kind2)) + { + case (CollectionExpressionTypeKind.ReadOnlySpan, CollectionExpressionTypeKind.Span): + case (CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span, _) when IsSZArrayOrArrayInterface(t2, out _): + return BetterResult.Left; + + case (CollectionExpressionTypeKind.Span, CollectionExpressionTypeKind.ReadOnlySpan): + case (_, CollectionExpressionTypeKind.ReadOnlySpan or CollectionExpressionTypeKind.Span) when IsSZArrayOrArrayInterface(t1, out _): + return BetterResult.Right; + } + } + + return BetterResult.Neither; } - private bool IsBetterCollectionExpressionConversion( + private bool IsBetterCollectionExpressionConversion_CSharp12( TypeSymbol t1, CollectionExpressionTypeKind kind1, TypeSymbol elementType1, TypeSymbol t2, CollectionExpressionTypeKind kind2, TypeSymbol elementType2, ref CompoundUseSiteInfo useSiteInfo) @@ -2914,12 +3033,26 @@ bool hasImplicitConversion(TypeSymbol source, TypeSymbol destination, ref Compou Conversions.ClassifyImplicitConversionFromType(source, destination, ref useSiteInfo).IsImplicit; } - private bool IsBetterParamsCollectionType(TypeSymbol t1, TypeSymbol t2, ref CompoundUseSiteInfo useSiteInfo) + private BetterResult BetterParamsCollectionType(TypeSymbol t1, TypeSymbol t2, ref CompoundUseSiteInfo useSiteInfo) { CollectionExpressionTypeKind kind1 = ConversionsBase.GetCollectionExpressionTypeKind(Compilation, t1, out TypeWithAnnotations elementType1); CollectionExpressionTypeKind kind2 = ConversionsBase.GetCollectionExpressionTypeKind(Compilation, t2, out TypeWithAnnotations elementType2); - return IsBetterCollectionExpressionConversion(t1, kind1, elementType1.Type, t2, kind2, elementType2.Type, ref useSiteInfo); + if (kind1 is CollectionExpressionTypeKind.CollectionBuilder or CollectionExpressionTypeKind.ImplementsIEnumerable) + { + _binder.TryGetCollectionIterationType(CSharpSyntaxTree.Dummy.GetRoot(), t1, out elementType1); + } + + if (kind2 is CollectionExpressionTypeKind.CollectionBuilder or CollectionExpressionTypeKind.ImplementsIEnumerable) + { + _binder.TryGetCollectionIterationType(CSharpSyntaxTree.Dummy.GetRoot(), t2, out elementType2); + } + + return BetterCollectionExpressionConversion( + collectionExpressionElements: [], + t1, kind1, elementType1.Type, underlyingElementConversions1: [], + t2, kind2, elementType2.Type, underlyingElementConversions2: [], + ref useSiteInfo); } private static bool IsSZArrayOrArrayInterface(TypeSymbol type, out TypeSymbol elementType) @@ -3142,7 +3275,7 @@ private BetterResult BetterConversionTargetCore( } private BetterResult BetterConversionTarget( - BoundExpression node, + BoundNode node, TypeSymbol type1, Conversion conv1, TypeSymbol type2, @@ -3154,7 +3287,7 @@ private BetterResult BetterConversionTarget( } private BetterResult BetterConversionTargetCore( - BoundExpression node, + BoundNode node, TypeSymbol type1, Conversion conv1, TypeSymbol type2, diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 6968f37801e2c..ce80ab75e5d3c 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -828,11 +828,16 @@ static void Main() { var x = F1([1]); var y = F2([2]); + x.Report(includeType: true); + y.Report(includeType: true); } } """; - var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( + var expectedOutput = "(System.Collections.Generic.List) [1], (System.Collections.Generic.List) [2], "; + CompileAndVerify([source, s_collectionExtensions], parseOptions: TestOptions.Regular13, expectedOutput: expectedOutput); + CompileAndVerify([source, s_collectionExtensions], parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput); + + CreateCompilation(source, parseOptions: TestOptions.Regular12).VerifyEmitDiagnostics( // (10,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(List)' and 'Program.F1(List)' // var x = F1([1]); Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(System.Collections.Generic.List)", "Program.F1(System.Collections.Generic.List)").WithLocation(10, 17), @@ -1021,18 +1026,44 @@ class Program static void Main() { var x = F([1, null]); + x.Report(includeType: true); int?[] y = [null, 2]; var z = F([..y]); + z.Report(includeType: true); + F([3, ..y]).Report(includeType: true); + F([..y, 4]).Report(includeType: true); + int[] w = [5, 6, 7]; + F([..y, ..w]).Report(includeType: true); + F([..w, ..y]).Report(includeType: true); } } """; - CreateCompilation(source).VerifyEmitDiagnostics( + var expectedOutput = "(System.Nullable[]) [1, null], (System.Nullable[]) [null, 2], (System.Nullable[]) [3, null, 2], " + + "(System.Nullable[]) [null, 2, 4], (System.Nullable[]) [null, 2, 5, 6, 7], (System.Nullable[]) [5, 6, 7, null, 2], "; + + CompileAndVerify([source, s_collectionExtensions], parseOptions: TestOptions.Regular13, + expectedOutput: expectedOutput); + CompileAndVerify([source, s_collectionExtensions], parseOptions: TestOptions.RegularPreview, + expectedOutput: expectedOutput); + CreateCompilation(source, parseOptions: TestOptions.Regular12).VerifyEmitDiagnostics( // (15,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' // var x = F([1, null]); Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(15, 17), - // (17,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' + // (18,17): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' // var z = F([..y]); - Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(17, 17) + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(18, 17), + // (20,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' + // F([3, ..y]).Report(includeType: true); + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(20, 9), + // (21,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' + // F([..y, 4]).Report(includeType: true); + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(21, 9), + // (23,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' + // F([..y, ..w]).Report(includeType: true); + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(23, 9), + // (24,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F(MyCollection)' and 'Program.F(int?[])' + // F([..w, ..y]).Report(includeType: true); + Diagnostic(ErrorCode.ERR_AmbigCall, "F").WithArguments("Program.F(MyCollection)", "Program.F(int?[])").WithLocation(24, 9) ); } @@ -1297,63 +1328,65 @@ public static RefStructConvertibleFromArray Create(scoped ReadOnlySpan """; [Theory] - [InlineData("System.Span", "T[]", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.IList", "System.Span")] - [InlineData("System.ReadOnlySpan", "T[]", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.ReadOnlySpan")] - [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)] // 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 - [InlineData("RefStructCollection", "GenericClassCollection", null, new[] { example_RefStructCollection, example_GenericClassCollection })] // cannot convert object to int - [InlineData("RefStructCollection", "NonGenericClassCollection", null, new[] { example_RefStructCollection, example_NonGenericClassCollection })] // rule requires span - [InlineData("GenericClassCollection", "T[]", null, new[] { example_GenericClassCollection })] // rule requires span - [InlineData("NonGenericClassCollection", "object[]", null, new[] { example_NonGenericClassCollection })] // rule requires span - [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "long[]", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "short[]", null)] // cannot convert int to short - [InlineData("System.ReadOnlySpan", "T[]", null)] // cannot convert long to int - [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.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) + [InlineData("System.Span", "T[]", "System.Span", "System.Span")] + [InlineData("System.Span", "int[]", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IList", "System.Span", "System.Span")] + [InlineData("System.ReadOnlySpan", "T[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.Span", "System.Collections.Generic.HashSet", null, null)] // rule requires array or array interface + [InlineData("System.Span", "System.ReadOnlySpan", null, "System.Span")] + [InlineData("RefStructCollection", "T[]", null, null, new[] { example_RefStructCollection })] + [InlineData("RefStructCollection", "RefStructCollection", null, "RefStructCollection", new[] { example_RefStructCollection })] + [InlineData("RefStructCollection", "GenericClassCollection", null, "RefStructCollection", new[] { example_RefStructCollection, example_GenericClassCollection })] + [InlineData("RefStructCollection", "GenericClassCollection", null, "GenericClassCollection", new[] { example_RefStructCollection, example_GenericClassCollection })] + [InlineData("RefStructCollection", "NonGenericClassCollection", null, "RefStructCollection", new[] { example_RefStructCollection, example_NonGenericClassCollection })] + [InlineData("GenericClassCollection", "T[]", null, null, new[] { example_GenericClassCollection })] // rule requires span + [InlineData("NonGenericClassCollection", "object[]", null, null, new[] { example_NonGenericClassCollection })] // rule requires span + [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "long[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "short[]", null, "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "int[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "T[]", null, "System.Int32[]")] + [InlineData("System.ReadOnlySpan", "long[]", null, "System.Int64[]")] + [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "string[]", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", null, "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null, "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", null, "System.ReadOnlySpan")] + [InlineData("System.Span", "System.Span", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Span", null, "System.Span")] + [InlineData("System.Span", "System.Span", null, "System.Span")] + [InlineData("System.Span", "System.Span", "System.Span", "System.Span")] + [InlineData("T[]", "int[]", "System.Int32[]", "System.Int32[]")] + [InlineData("T[]", "object[]", null, "System.Int32[]")] + [InlineData("T[]", "int?[]", null, "System.Int32[]")] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", null, "System.Collections.Generic.ICollection")] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection", null, "System.Collections.Generic.ICollection")] + [InlineData("System.Collections.Generic.ICollection", "System.Collections.Generic.IReadOnlyCollection", null, null)] + [InlineData("MyCollectionA", "MyCollectionB", "MyCollectionB", "MyCollectionB", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", "MyCollectionB", "MyCollectionB", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", null, "MyCollectionA", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionA", "MyCollectionB", null, "MyCollectionA", new[] { example_GenericClassesWithConversion })] + [InlineData("MyCollectionB", "MyCollectionB", null, "MyCollectionB", new[] { example_GenericClassesWithConversion })] + [InlineData("RefStructConvertibleFromArray", "T[]", "System.Int32[]", "System.Int32[]", new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "int[]", "System.Int32[]", "System.Int32[]", new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "T[]", null, "System.Int32[]", new[] { example_RefStructConvertibleFromArray })] + [InlineData("RefStructConvertibleFromArray", "object[]", null, "RefStructConvertibleFromArray", new[] { example_RefStructConvertibleFromArray })] + public void BetterConversionFromExpression_01A(string type1, string type2, string expectedType12, string expectedType13, string[] additionalSources = null) { string source = $$""" using System; @@ -1372,10 +1405,131 @@ static void Main() } } """; + + verify(TestOptions.Regular12, expectedType12); + verify(TestOptions.Regular13, expectedType13); + verify(TestOptions.RegularPreview, expectedType13); + + 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(); + } + + void verify(CSharpParseOptions parseOptions, string expectedType) + { + var comp = CreateCompilation( + getSources(source, additionalSources), + targetFramework: TargetFramework.Net80, + parseOptions: parseOptions, + 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)); + } + } + } + + [Theory] + [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Span", null)] + [InlineData("System.ReadOnlySpan", "System.Span", null)] + [InlineData("System.ReadOnlySpan", "System.Span", null)] + [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?[]", null)] + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", null)] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", null)] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", null)] + [InlineData("System.Span", "System.Collections.Generic.ICollection", null)] + [InlineData("System.Span", "System.Collections.Generic.IList", null)] + [InlineData("System.Span", "int[]", null)] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", null)] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", null)] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", null)] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.ICollection", null)] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.IList", null)] // cannot convert int? to int + [InlineData("System.ReadOnlySpan", "object[]", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", null)] + [InlineData("System.ReadOnlySpan", "int[]", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", null)] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", null)] // cannot convert object to int + [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 + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.IEnumerable", "System.Collections.Generic.List", null)] + [InlineData("int[]", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] + public void BetterConversionFromExpression_01B_Empty(string type1, string type2, string expectedType) + { + string source = $$""" + using System; + class Program + { + {{generateMethod("F1", type1)}} + {{generateMethod("F1", type2)}} + {{generateMethod("F2", type2)}} + {{generateMethod("F2", type1)}} + static void Main() + { + var a = F1([]); + Console.WriteLine(a.GetTypeName()); + var b = F2([]); + Console.WriteLine(b.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($""" @@ -1386,37 +1540,126 @@ 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]); + // 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)); } - 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})"; + $"Program.{methodName}({parameterType})"; + } - static string[] getSources(string source, string[] additionalSources) + [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", "System.Span")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan>")] + [InlineData("System.Span", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Span", "System.Span")] + [InlineData("System.Span", "System.Span", "System.Span>")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.Span", "int?[]", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Span")] + [InlineData("System.Span", "System.Collections.Generic.IList", "System.Span")] + [InlineData("System.Span", "int[]", "System.Int32[]")] + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] + [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.Span", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] + [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "int[]", "System.Int32[]")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.List")] + [InlineData("int[]", "object[]", "System.Int32[]")] + [InlineData("int[]", "System.Collections.Generic.IReadOnlyList", "System.Int32[]")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List>")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List")] + [InlineData("System.Collections.Generic.IEnumerable", "System.Collections.Generic.List", "System.Collections.Generic.IEnumerable")] + [InlineData("int[]", "System.Collections.Generic.List", "System.Int32[]")] + [InlineData("System.Collections.Generic.HashSet", "System.Span", "System.Collections.Generic.HashSet")] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", "System.Collections.Generic.HashSet")] + [InlineData("System.Collections.Generic.HashSet", "System.Span", "System.Span")] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", "System.ReadOnlySpan")] + public void BetterConversionFromExpression_01B_NotEmpty(string type1, string type2, string expectedType) + { + string source = $$""" + using System; + class Program + { + {{generateMethod("F1", type1)}} + {{generateMethod("F1", type2)}} + {{generateMethod("F2", type2)}} + {{generateMethod("F2", type1)}} + static void Main() + { + var c = F1([1, 2, 3]); + Console.WriteLine(c.GetTypeName()); + var d = F2([4, 5]); + Console.WriteLine(d.GetTypeName()); + } + } + """; + var comp = CreateCompilation( + new[] { source, s_collectionExtensions }, + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + + if (expectedType is { }) { - var builder = ArrayBuilder.GetInstance(); - builder.Add(source); - builder.Add(s_collectionExtensions); - if (additionalSources is { }) builder.AddRange(additionalSources); - return builder.ToArrayAndFree(); + 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(int[])' and 'Program.F1(object[])' + // var c = 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(object[])' and 'Program.F2(int[])' + // var d = F2(4, 5); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(generateMethodSignature("F2", type2), generateMethodSignature("F2", type1)).WithLocation(12, 17)); + } + + static string generateMethod(string methodName, string parameterType) => + $"static Type {methodName}({parameterType} value) => typeof({parameterType});"; + + static string generateMethodSignature(string methodName, string parameterType) => + $"Program.{methodName}({parameterType})"; } [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", "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)] @@ -1453,7 +1696,17 @@ static string[] getSources(string source, string[] additionalSources) [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_01B(string type1, string type2, string expectedType) + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.IEnumerable", "System.Collections.Generic.List", null)] + [InlineData("int[]", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] + public void BetterConversionFromExpression_01B_CSharp12(string type1, string type2, string expectedType) { string source = $$""" using System; @@ -1479,6 +1732,7 @@ static void Main() var comp = CreateCompilation( new[] { source, s_collectionExtensions }, targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, options: TestOptions.ReleaseExe); if (expectedType is { }) { @@ -1513,6 +1767,104 @@ static string generateMethodSignature(string methodName, string parameterType) = $"Program.{methodName}({parameterType})"; } + [Theory, CombinatorialData] + public void BetterConversionFromExpression_01C( + [CombinatorialValues( + "System.ReadOnlySpan", + "System.Span", + "string[]", + "System.Collections.Generic.IEnumerable", + "System.Collections.Generic.IReadOnlyList", + "System.Collections.Generic.IReadOnlyCollection", + "System.Collections.Generic.IList", + "System.Collections.Generic.ICollection" + )] + string stringType, + [CombinatorialValues( + "System.ReadOnlySpan", + "System.Span", + "CustomHandler[]", + "System.Collections.Generic.IEnumerable", + "System.Collections.Generic.IReadOnlyList", + "System.Collections.Generic.IReadOnlyCollection", + "System.Collections.Generic.IList", + "System.Collections.Generic.ICollection")] + string interpolatedType) + { + var testMethods = $$""" + partial class Program + { + {{generateMethod("F1", stringType)}} + {{generateMethod("F1", interpolatedType)}} + {{generateMethod("F2", interpolatedType)}} + {{generateMethod("F2", stringType)}} + } + """; + + var customHandler = GetInterpolatedStringCustomHandlerType("CustomHandler", "class", useBoolReturns: false, includeOneTimeHelpers: false); + + string source1 = $$""" + var a = F1([]); + var b = F2([]); + var c = F1([$"{1}", $"2", $"{3}"]); + var d = F2([$"{1}", $"2", $"{3}"]); + """; + var comp = CreateCompilation( + [source1, testMethods, customHandler, s_collectionExtensions], + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + + string f1InterpolatedSig = generateMethodSignature("F1", interpolatedType); + string f1StringSig = generateMethodSignature("F1", stringType); + string f2InterpolatedSig = generateMethodSignature("F2", interpolatedType); + string f2StringSig = generateMethodSignature("F2", stringType); + comp.VerifyDiagnostics( + // (1,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(IReadOnlyList)' and 'Program.F1(IReadOnlyCollection)' + // var a = F1([]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(f1StringSig, f1InterpolatedSig).WithLocation(1, 9), + // (2,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(IReadOnlyCollection)' and 'Program.F2(IReadOnlyList)' + // var b = F2([]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(f2InterpolatedSig, f2StringSig).WithLocation(2, 9), + // (3,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(string[])' and 'Program.F1(CustomHandler[])' + // var c = F1([$"{1}", $"2", $"{3}"]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(f1StringSig, f1InterpolatedSig).WithLocation(3, 9), + // (4,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(CustomHandler[])' and 'Program.F2(string[])' + // var d = F2([$"{1}", $"2", $"{3}"]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(f2InterpolatedSig, f2StringSig).WithLocation(4, 9) + ); + + var source2 = $$""" + using System; + var c = F1([$"{1}", $"{2}", $"{3}"]); + Console.WriteLine(c.GetTypeName()); + var d = F2([$"{1}", $"{2}", $"{3}"]); + Console.WriteLine(d.GetTypeName()); + var e = F1([$"1", $"2", $"3"]); + Console.WriteLine(e.GetTypeName()); + var f = F2([$"1", $"2", $"3"]); + Console.WriteLine(f.GetTypeName()); + """; + + comp = CreateCompilation( + [source2, testMethods, customHandler, s_collectionExtensions], + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + + string outputStringType = stringType.Replace("string", "System.String"); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput($""" + {interpolatedType} + {interpolatedType} + {outputStringType} + {outputStringType} + """)); + + static string generateMethod(string methodName, string parameterType) => + $"static System.Type {methodName}({parameterType} value) => typeof({parameterType});"; + + static string generateMethodSignature(string methodName, string parameterType) => + $"Program.{methodName}({parameterType})"; + } + [Fact] public void BetterConversionFromExpression_02() { @@ -1548,21 +1900,46 @@ static void Main() Generic([string.Empty]); // Span Identical([string.Empty]); // Span SpanDerived([string.Empty]); // Span + ArrayDerived([string.Empty]); // string[] } } """; - var comp = CreateCompilation( - new[] { sourceA, sourceB1 }, - targetFramework: TargetFramework.Net80, - options: TestOptions.ReleaseExe); - CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(""" + + string expectedOutput = IncludeExpectedOutput(""" T[] string[] string[] Span Span Span - """)); + string[] + """); + + var comp = CreateCompilation( + new[] { sourceA, sourceB1 }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + options: TestOptions.ReleaseExe); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: expectedOutput); + + comp = CreateCompilation( + new[] { sourceA, sourceB1 }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + options: TestOptions.ReleaseExe); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: expectedOutput); + + comp = CreateCompilation( + new[] { sourceA, sourceB1 }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, + options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // 1.cs(12,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.ArrayDerived(Span)' and 'Program.ArrayDerived(string[])' + // ArrayDerived([string.Empty]); // string[] + Diagnostic(ErrorCode.ERR_AmbigCall, "ArrayDerived").WithArguments("Program.ArrayDerived(System.Span)", "Program.ArrayDerived(string[])").WithLocation(12, 9) + ); string sourceB2 = """ partial class Program @@ -1570,20 +1947,31 @@ partial class Program static void Main() { SpanDerived(new[] { string.Empty }); // ambiguous - ArrayDerived([string.Empty]); // ambiguous } } """; + + // 1.cs(5,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.SpanDerived(Span)' and 'Program.SpanDerived(object[])' + // SpanDerived(new[] { string.Empty }); // ambiguous + var expectedDiagnostic = Diagnostic(ErrorCode.ERR_AmbigCall, "SpanDerived").WithArguments("Program.SpanDerived(System.Span)", "Program.SpanDerived(object[])").WithLocation(5, 9); + comp = CreateCompilation( new[] { sourceA, sourceB2 }, + parseOptions: TestOptions.Regular13, targetFramework: TargetFramework.Net80); - comp.VerifyEmitDiagnostics( - // 1.cs(5,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.SpanDerived(Span)' and 'Program.SpanDerived(object[])' - // SpanDerived(new[] { string.Empty }); // ambiguous - Diagnostic(ErrorCode.ERR_AmbigCall, "SpanDerived").WithArguments("Program.SpanDerived(System.Span)", "Program.SpanDerived(object[])").WithLocation(5, 9), - // 1.cs(6,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.ArrayDerived(Span)' and 'Program.ArrayDerived(string[])' - // ArrayDerived([string.Empty]); // ambiguous - Diagnostic(ErrorCode.ERR_AmbigCall, "ArrayDerived").WithArguments("Program.ArrayDerived(System.Span)", "Program.ArrayDerived(string[])").WithLocation(6, 9)); + comp.VerifyEmitDiagnostics(expectedDiagnostic); + + comp = CreateCompilation( + new[] { sourceA, sourceB2 }, + parseOptions: TestOptions.RegularPreview, + targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(expectedDiagnostic); + + comp = CreateCompilation( + new[] { sourceA, sourceB2 }, + parseOptions: TestOptions.Regular12, + targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(expectedDiagnostic); } [WorkItem("https://github.com/dotnet/roslyn/issues/69634")] @@ -1668,9 +2056,31 @@ static void Main() } } """; + var expectedDiagnostics = new[] { + // 'params' works in this case. + // For 'Inline collection expression' case it fails because: + // - For the first argument, 'int[]' and 'Span' -> `int[]` is better vs. `Span` for 'params' + // - For the second argument, 'int' and 'int' -> 'ReadOnlySpan' is better vs. neither is better for 'params' + // The first parameter is better for the first overload, and the second is better for the second overload, ambiguous. + + // 0.cs(10,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(int[], int[])' and 'Program.F1(Span, ReadOnlySpan)' + // F1([1], [2]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(int[], int[])", "Program.F1(System.Span, System.ReadOnlySpan)").WithLocation(10, 9), + // 0.cs(11,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(object, string[])' and 'Program.F2(string, Span)' + // F2("3", ["4"]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(object, string[])", "Program.F2(string, System.Span)").WithLocation(11, 9) + }; + + var comp = CreateCompilation(new[] { source, s_collectionExtensionsWithSpan }, parseOptions: TestOptions.Regular13, targetFramework: TargetFramework.Net80); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, s_collectionExtensionsWithSpan }, parseOptions: TestOptions.RegularPreview, targetFramework: TargetFramework.Net80); + comp.VerifyDiagnostics(expectedDiagnostics); + CompileAndVerify( new[] { source, s_collectionExtensionsWithSpan }, targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("[1], [2], [4], ")); } @@ -1773,10 +2183,10 @@ public void BetterConversionFromExpression_07() 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 F1(ReadOnlySpan value) => Console.WriteLine("ReadOnlySpan"); + static void F1(ReadOnlySpan value) => Console.WriteLine("ReadOnlySpan"); + static void F2(Span value) => Console.WriteLine("Span"); + static void F2(Span value) => Console.WriteLine("Span"); static void Main() { F1([1, 2, 3]); @@ -1784,10 +2194,25 @@ static void Main() } } """; - var comp = CreateCompilation( + var expectedOutput = IncludeExpectedOutput(""" + ReadOnlySpan + Span + """); + CompileAndVerify( source, - targetFramework: TargetFramework.Net80); - comp.VerifyEmitDiagnostics( + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + verify: Verification.Skipped, + expectedOutput: expectedOutput); + + CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + verify: Verification.Skipped, + expectedOutput: expectedOutput); + + CreateCompilation(source, parseOptions: TestOptions.Regular12, targetFramework: TargetFramework.Net80).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), @@ -1800,21 +2225,25 @@ static void Main() public void BetterConversionFromExpression_08A() { string source = """ + using System; class Program { - static void F1(int[] value) { } - static void F1(object[] value) { } + static void F1(int[] value) => Console.WriteLine("int[]"); + static void F1(object[] value) => Console.WriteLine("object[]"); 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[])' + var expectedOutput = "int[]"; + CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: expectedOutput).VerifyDiagnostics(); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput).VerifyDiagnostics(); + + CreateCompilation(source, parseOptions: TestOptions.Regular12).VerifyEmitDiagnostics( + // (8,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)); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(int[])", "Program.F1(object[])").WithLocation(8, 9)); } [Fact] @@ -1835,6 +2264,687 @@ static void Main() CompileAndVerify(source, expectedOutput: "string[]"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73857")] + public void BetterConversionFromExpression_08C() + { + string source = """ + using System; + class Program + { + static void F2(ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void F2(ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void Main() + { + F2(["a", "b"]); + } + } + """; + CompileAndVerify(source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("ReadOnlySpan")); + } + + [Theory] + [InlineData(LanguageVersion.Preview)] + [InlineData(LanguageVersion.CSharp13)] + [InlineData(LanguageVersion.CSharp12)] + public void BetterConversionFromExpression_09(LanguageVersion version) + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void F2(List value) { Console.WriteLine("List"); } + static void F2(List value) { Console.WriteLine("List"); } + static void Main() + { + F2([1, (byte)2]); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(version)).VerifyDiagnostics( + // (9,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(List)' and 'Program.F2(List)' + // F2([1, (byte)2]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(System.Collections.Generic.List)", "Program.F2(System.Collections.Generic.List)").WithLocation(9, 9)); + } + + [Fact] + public void BetterConversionFromExpression_10() + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void F2(List value) { Console.WriteLine("List"); } + static void F2(List value) { Console.WriteLine("List"); } + static void Main() + { + F2([(byte)1, (byte)2]); + } + } + """; + + var expectedOutput = "List"; + CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: expectedOutput); + CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: expectedOutput); + + CreateCompilation(source, parseOptions: TestOptions.Regular12).VerifyDiagnostics( + // (9,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(List)' and 'Program.F2(List)' + // F2([1, (byte)2]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(System.Collections.Generic.List)", "Program.F2(System.Collections.Generic.List)").WithLocation(9, 9)); + } + + [Theory] + [InlineData("System.FormattableString")] + [InlineData("System.IFormattable")] + public void BetterConversionFromExpression_11(string formatType) + { + string source = $$""" + using System; + class Program + { + static void F2(ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void F2(ReadOnlySpan<{{formatType}}> value) { Console.WriteLine("ReadOnlySpan<{{formatType}}>"); } + static void Main() + { + F2([$"{1}"]); + } + } + """; + + string expectedOutput = IncludeExpectedOutput("ReadOnlySpan"); + + CompileAndVerify(source, + parseOptions: TestOptions.Regular13, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + parseOptions: TestOptions.RegularPreview, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: expectedOutput); + + CreateCompilation(source, parseOptions: TestOptions.Regular12, targetFramework: TargetFramework.Net80).VerifyDiagnostics( + // (8,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(ReadOnlySpan)' and 'Program.F2(ReadOnlySpan)' + // F2([$"{1}"]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(System.ReadOnlySpan)", $"Program.F2(System.ReadOnlySpan<{formatType}>)").WithLocation(8, 9) + ); + } + + [Fact] + public void BetterConversionFromExpression_12() + { + string source = """ + using System; + + M1([$"{1}"]); + + partial class Program + { + static void M1(System.ReadOnlySpan x) => Console.WriteLine("ReadOnlySpan"); + static void M1(System.Span x) => Console.WriteLine("Span"); + } + + partial class CustomHandler + { + public static implicit operator CustomHandler(string s) => new CustomHandler(0, 0); + } + """; + + var handler = GetInterpolatedStringCustomHandlerType("CustomHandler", "partial class", useBoolReturns: false, includeOneTimeHelpers: false); + + string expectedOutput13 = IncludeExpectedOutput("Span"); + + CompileAndVerify( + new[] { source, handler }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + verify: Verification.Skipped, + expectedOutput: expectedOutput13); + + CompileAndVerify( + new[] { source, handler }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + verify: Verification.Skipped, + expectedOutput: expectedOutput13); + + CompileAndVerify( + new[] { source, handler }, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("ReadOnlySpan")); + } + + [Theory] + [CombinatorialData] + public void BetterConversionFromExpression_13(bool dynamicReadOnlySpan) + { + var spanType = dynamicReadOnlySpan ? "Span" : "Span"; + var readOnlySpanType = dynamicReadOnlySpan ? "ReadOnlySpan" : "ReadOnlySpan"; + + var source = $$""" + using System; + using System.Collections.Generic; + + object o = null; + dynamic d = null; + + M1([o, o]); + M1([d, d]); + M1([o, d]); + M1([d, o]); + M2([o, o]); + M2([d, d]); + M2([o, d]); + M2([d, o]); + + IEnumerable x = []; + IEnumerable y = []; + M1([..x]); + M1([..y]); + M1([o, ..x]); + M1([d, ..x]); + M1([..y, o]); + M1([..y, d]); + M1([..x, ..y]); + M1([..y, ..x]); + + partial class Program + { + static void M1({{spanType}} s) => Console.WriteLine("{{spanType}}"); + static void M1({{readOnlySpanType}} s) => Console.WriteLine("{{readOnlySpanType}}"); + static void M2({{readOnlySpanType}} s) => Console.WriteLine("{{readOnlySpanType}}"); + static void M2({{spanType}} s) => Console.WriteLine("{{spanType}}"); + } + """; + + string expectedOutput = IncludeExpectedOutput($""" + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + {readOnlySpanType} + """); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, + expectedOutput: expectedOutput); + } + + [Fact] + public void BetterConversionFromExpression_14() + { + var source = $$""" + using System.Collections.Generic; + + object o = null; + dynamic d = null; + + o.M1([o, o]); + o.M1([d, d]); + o.M1([o, d]); + o.M1([d, o]); + + IEnumerable x = []; + IEnumerable y = []; + o.M1([..x]); + o.M1([..y]); + o.M1([o, ..x]); + o.M1([d, ..x]); + o.M1([..y, o]); + o.M1([..y, d]); + o.M1([..x, ..y]); + o.M1([..y, ..x]); + + static class Ext1 + { + public static void M1(this object o, List d) { } + } + + static class Ext2 + { + public static void M1(this object o, List d) { } + } + """; + + var expectedDiagnostics = new[] { + // (6,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([o, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(6, 3), + // (7,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([d, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(7, 3), + // (8,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([o, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(8, 3), + // (9,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([d, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(9, 3), + // (13,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(13, 3), + // (14,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(14, 3), + // (15,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([o, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(15, 3), + // (16,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([d, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(16, 3), + // (17,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..y, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(17, 3), + // (18,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..y, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(18, 3), + // (19,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..x, ..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(19, 3), + // (20,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ext1.M1(object, List)' and 'Ext2.M1(object, List)' + // o.M1([..y, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ext1.M1(object, System.Collections.Generic.List)", "Ext2.M1(object, System.Collections.Generic.List)").WithLocation(20, 3) + }; + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory] + [CombinatorialData] + public void BetterConversionFromExpression_15(bool dynamicList) + { + var ienumerableType = dynamicList ? "IEnumerable" : "IEnumerable"; + var listType = dynamicList ? "List" : "List"; + + var source = $$""" + using System; + using System.Collections.Generic; + + object o = null; + dynamic d = null; + + M1([o, o]); + M1([d, d]); + M1([o, d]); + M1([d, o]); + M2([o, o]); + M2([d, d]); + M2([o, d]); + M2([d, o]); + + IEnumerable x = []; + IEnumerable y = []; + M1([..x]); + M1([..y]); + M1([o, ..x]); + M1([d, ..x]); + M1([..y, o]); + M1([..y, d]); + M1([..x, ..y]); + M1([..y, ..x]); + + partial class Program + { + static void M1({{ienumerableType}} s) => Console.WriteLine("{{ienumerableType}}"); + static void M1({{listType}} s) => Console.WriteLine("{{listType}}"); + static void M2({{listType}} s) => Console.WriteLine("{{listType}}"); + static void M2({{ienumerableType}} s) => Console.WriteLine("{{ienumerableType}}"); + } + """; + + string expectedOutput = IncludeExpectedOutput($""" + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + {listType} + """); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, + expectedOutput: expectedOutput); + } + + [Theory] + [CombinatorialData] + public void BetterConversionFromExpression_16(bool dynamicList) + { + var hashSetType = dynamicList ? "HashSet" : "HashSet"; + var listType = dynamicList ? "List" : "List"; + + var source = $$""" + using System.Collections.Generic; + + object o = null; + dynamic d = null; + + M1([o, o]); + M1([d, d]); + M1([o, d]); + M1([d, o]); + M2([o, o]); + M2([d, d]); + M2([o, d]); + M2([d, o]); + + IEnumerable x = []; + IEnumerable y = []; + M1([..x]); + M1([..y]); + M1([o, ..x]); + M1([d, ..x]); + M1([..y, o]); + M1([..y, d]); + M1([..x, ..y]); + M1([..y, ..x]); + + partial class Program + { + static void M1({{hashSetType}} s) { } + static void M1({{listType}} s) { } + static void M2({{listType}} s) { } + static void M2({{hashSetType}} s) { } + } + """; + + var expectedDiagnostics = new[] { + // (6,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([o, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(6, 1), + // (7,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([d, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(7, 1), + // (8,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([o, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(8, 1), + // (9,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([d, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(9, 1), + // (10,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M2(List)' and 'Program.M2(HashSet)' + // M2([o, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments(generateMethodSignature("M2", listType),generateMethodSignature("M2", hashSetType)).WithLocation(10, 1), + // (11,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M2(List)' and 'Program.M2(HashSet)' + // M2([d, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments(generateMethodSignature("M2", listType),generateMethodSignature("M2", hashSetType)).WithLocation(11, 1), + // (12,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M2(List)' and 'Program.M2(HashSet)' + // M2([o, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments(generateMethodSignature("M2", listType),generateMethodSignature("M2", hashSetType)).WithLocation(12, 1), + // (13,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M2(List)' and 'Program.M2(HashSet)' + // M2([d, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M2").WithArguments(generateMethodSignature("M2", listType),generateMethodSignature("M2", hashSetType)).WithLocation(13, 1), + // (17,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(17, 1), + // (18,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(18, 1), + // (19,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([o, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(19, 1), + // (20,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([d, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(20, 1), + // (21,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..y, o]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(21, 1), + // (22,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..y, d]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(22, 1), + // (23,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..x, ..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(23, 1), + // (24,1): error CS0121: The call is ambiguous between the following methods or properties: 'Program.M1(HashSet)' and 'Program.M1(List)' + // M1([..y, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments(generateMethodSignature("M1", hashSetType), generateMethodSignature("M1", listType)).WithLocation(24, 1) + }; + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12).VerifyDiagnostics(expectedDiagnostics); + + static string generateMethodSignature(string methodName, string parameterType) => + $"Program.{methodName}(System.Collections.Generic.{parameterType})"; + } + + [Fact] + public void BetterConversionFromExpression_17() + { + var source = $$""" + using System; + using System.Collections.Generic; + + M1([(a: 1, b: 2)]); + M1([(a: 1, d: 2)]); + M1([(c: 1, b: 2)]); + M1([(c: 1, d: 2)]); + M2([(a: 1, b: 2)]); + M2([(a: 1, d: 2)]); + M2([(c: 1, b: 2)]); + M2([(c: 1, d: 2)]); + + IEnumerable<(int a, int b)> x = []; + IEnumerable<(int c, int d)> y = []; + M1([..x]); + M1([..y]); + M1([(a: 1, b: 2), ..x]); + M1([(c: 3, d: 4), ..x]); + M1([..y, (a: 5, b: 6)]); + M1([..y, (c: 7, d: 8)]); + M1([..x, ..y]); + M1([..y, ..x]); + + partial class Program + { + static void M1(Span<(int a, int b)> s) => Console.WriteLine("a, b"); + static void M1(ReadOnlySpan<(int c, int d)> s) => Console.WriteLine("c, d"); + static void M2(ReadOnlySpan<(int c, int d)> s) => Console.WriteLine("c, d"); + static void M2(Span<(int a, int b)> s) => Console.WriteLine("a, b"); + } + """; + + string expectedOutput = IncludeExpectedOutput($""" + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + c, d + """); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview, + expectedOutput: expectedOutput); + + CompileAndVerify(source, + verify: Verification.Skipped, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12, + expectedOutput: expectedOutput); + } + + [Fact] + public void BetterConversionFromExpression_18() + { + var source = $$""" + using System; + using System.Collections.Generic; + + object o = null; + + o.M1([(a: 1, b: 2)]); + o.M1([(a: 1, d: 2)]); + o.M1([(c: 1, b: 2)]); + o.M1([(c: 1, d: 2)]); + + IEnumerable<(int a, int b)> x = []; + IEnumerable<(int c, int d)> y = []; + o.M1([..x]); + o.M1([..y]); + o.M1([(a: 1, b: 2), ..x]); + o.M1([(c: 3, d: 4), ..x]); + o.M1([..y, (a: 5, b: 6)]); + o.M1([..y, (c: 7, d: 8)]); + o.M1([..x, ..y]); + o.M1([..y, ..x]); + + + static class Ex1 + { + public static void M1(this object o, List<(int a, int b)> s) => Console.WriteLine("a, b"); + } + + static class Ex2 + { + public static void M1(this object o, List<(int c, int d)> s) => Console.WriteLine("c, d"); + } + """; + + var expectedDiagnostics = new[] { + // (6,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(a: 1, b: 2)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(6, 3), + // (7,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(a: 1, d: 2)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(7, 3), + // (8,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(c: 1, b: 2)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(8, 3), + // (9,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(c: 1, d: 2)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(9, 3), + // (13,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(13, 3), + // (14,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(14, 3), + // (15,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(a: 1, b: 2), ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(15, 3), + // (16,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([(c: 3, d: 4), ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(16, 3), + // (17,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..y, (a: 5, b: 6)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(17, 3), + // (18,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..y, (c: 7, d: 8)]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(18, 3), + // (19,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..x, ..y]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(19, 3), + // (20,3): error CS0121: The call is ambiguous between the following methods or properties: 'Ex1.M1(object, List<(int a, int b)>)' and 'Ex2.M1(object, List<(int c, int d)>)' + // o.M1([..y, ..x]); + Diagnostic(ErrorCode.ERR_AmbigCall, "M1").WithArguments("Ex1.M1(object, System.Collections.Generic.List<(int a, int b)>)", "Ex2.M1(object, System.Collections.Generic.List<(int c, int d)>)").WithLocation(20, 3) + }; + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular13).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(expectedDiagnostics); + + CreateCompilation(source, + targetFramework: TargetFramework.Net80, + parseOptions: TestOptions.Regular12).VerifyDiagnostics(expectedDiagnostics); + } + [Theory] [InlineData("System.ReadOnlySpan")] [InlineData("System.Span")] diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs index f1cede6d71014..c90bf7d92467c 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs @@ -5108,8 +5108,8 @@ static string[] getSources(string source, string[] additionalSources) [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)] + [InlineData("System.ReadOnlySpan", "System.Span", null)] [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)] @@ -5119,24 +5119,24 @@ static string[] getSources(string source, string[] additionalSources) [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")] - [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Span")] - [InlineData("System.Span", "System.Collections.Generic.IList", "System.Span")] + [InlineData("System.Span", "int?[]", null)] + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", null)] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", null)] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", null)] + [InlineData("System.Span", "System.Collections.Generic.ICollection", null)] + [InlineData("System.Span", "System.Collections.Generic.IList", null)] [InlineData("System.Span", "int[]", null)] // cannot convert int? to int [InlineData("System.Span", "System.Collections.Generic.IEnumerable", null)] // cannot convert int? to int [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", null)] // cannot convert int? to int [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", null)] // cannot convert int? to int [InlineData("System.Span", "System.Collections.Generic.ICollection", null)] // cannot convert int? to int [InlineData("System.Span", "System.Collections.Generic.IList", null)] // cannot convert int? to int - [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.ReadOnlySpan")] - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "object[]", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", null)] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", null)] [InlineData("System.ReadOnlySpan", "int[]", null)] // cannot convert object to int [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", null)] // cannot convert object to int [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", null)] // cannot convert object to int @@ -5146,6 +5146,16 @@ static string[] getSources(string source, string[] additionalSources) [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 + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.IEnumerable", "System.Collections.Generic.List", null)] + [InlineData("int[]", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] + [InlineData("System.Collections.Generic.HashSet", "System.Span", null)] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", null)] public void BetterConversionFromExpression_01B_Empty(string type1, string type2, string expectedType) // This is a clone of a unit-test from CollectionExpressionTests.cs { string source = $$""" @@ -5198,24 +5208,14 @@ static string generateMethodSignature(string methodName, string parameterType) = [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "System.Span", "System.ReadOnlySpan")] - - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case + [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] + [InlineData("System.ReadOnlySpan", "System.Span", "System.Span")] [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] - // Ambiguous for inline collection expression, but 'int?' is a better conversion target than 'object' in params case [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan>")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case [InlineData("System.Span", "System.Span", "System.Span")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case [InlineData("System.Span", "System.Span", "System.Span")] - // Ambiguous for inline collection expression, but 'int?' is a better conversion target than 'object' in params case [InlineData("System.Span", "System.Span", "System.Span>")] - // Ambiguous for inline collection expression, but 'long' is a better conversion target than 'object' in params case [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan", "System.ReadOnlySpan")] [InlineData("System.Span", "int?[]", "System.Span")] @@ -5225,18 +5225,12 @@ static string generateMethodSignature(string methodName, string parameterType) = [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Span")] [InlineData("System.Span", "System.Collections.Generic.IList", "System.Span")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case [InlineData("System.Span", "int[]", "System.Int32[]")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] // cannot convert int? to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'int?' in params case - [InlineData("System.Span", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] // cannot convert int? to int + [InlineData("System.Span", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] + [InlineData("System.Span", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] + [InlineData("System.Span", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.Span", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] [InlineData("System.ReadOnlySpan", "object[]", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.ReadOnlySpan")] @@ -5245,25 +5239,28 @@ static string generateMethodSignature(string methodName, string parameterType) = [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.ReadOnlySpan")] [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.ReadOnlySpan")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "int[]", "System.Int32[]")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] // cannot convert object to int - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] // cannot convert object to int + [InlineData("System.ReadOnlySpan", "int[]", "System.Int32[]")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.IEnumerable")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyCollection", "System.Collections.Generic.IReadOnlyCollection")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IReadOnlyList", "System.Collections.Generic.IReadOnlyList")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.ICollection", "System.Collections.Generic.ICollection")] + [InlineData("System.ReadOnlySpan", "System.Collections.Generic.IList", "System.Collections.Generic.IList")] [InlineData("System.Collections.Generic.List", "System.Collections.Generic.IEnumerable", "System.Collections.Generic.List")] - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("int[]", "object[]", "System.Int32[]")] // rule requires span - // Ambiguous for inline collection expression, but 'int' is a better conversion target than 'object' in params case - [InlineData("int[]", "System.Collections.Generic.IReadOnlyList", "System.Int32[]")] // rule requires span + [InlineData("int[]", "object[]", "System.Int32[]")] + [InlineData("int[]", "System.Collections.Generic.IReadOnlyList", "System.Int32[]")] + + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", null)] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List>")] + [InlineData("System.Collections.Generic.List", "System.Collections.Generic.List", "System.Collections.Generic.List")] + [InlineData("System.Collections.Generic.IEnumerable", "System.Collections.Generic.List", "System.Collections.Generic.IEnumerable")] + [InlineData("int[]", "System.Collections.Generic.List", "System.Int32[]")] + [InlineData("System.Collections.Generic.HashSet", "System.Span", "System.Collections.Generic.HashSet")] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", "System.Collections.Generic.HashSet")] + [InlineData("System.Collections.Generic.HashSet", "System.Span", "System.Span")] + [InlineData("System.Collections.Generic.HashSet", "System.ReadOnlySpan", "System.ReadOnlySpan")] public void BetterConversionFromExpression_01B_NotEmpty(string type1, string type2, string expectedType) // This is a clone of a unit-test from CollectionExpressionTests.cs { string source = $$""" @@ -5312,6 +5309,104 @@ static string generateMethodSignature(string methodName, string parameterType) = $"Program.{methodName}(params {parameterType})"; } + [Theory, CombinatorialData] + public void BetterConversionFromExpression_01C( + [CombinatorialValues( + "System.ReadOnlySpan", + "System.Span", + "string[]", + "System.Collections.Generic.IEnumerable", + "System.Collections.Generic.IReadOnlyList", + "System.Collections.Generic.IReadOnlyCollection", + "System.Collections.Generic.IList", + "System.Collections.Generic.ICollection" + )] + string stringType, + [CombinatorialValues( + "System.ReadOnlySpan", + "System.Span", + "CustomHandler[]", + "System.Collections.Generic.IEnumerable", + "System.Collections.Generic.IReadOnlyList", + "System.Collections.Generic.IReadOnlyCollection", + "System.Collections.Generic.IList", + "System.Collections.Generic.ICollection")] + string interpolatedType) // This is a clone of a unit-test from CollectionExpressionTests.cs + { + var testMethods = $$""" + partial class Program + { + {{generateMethod("F1", stringType)}} + {{generateMethod("F1", interpolatedType)}} + {{generateMethod("F2", interpolatedType)}} + {{generateMethod("F2", stringType)}} + } + """; + + var customHandler = GetInterpolatedStringCustomHandlerType("CustomHandler", "class", useBoolReturns: false, includeOneTimeHelpers: false); + + string source1 = $$""" + var a = F1(); + var b = F2(); + var c = F1($"{1}", $"2", $"{3}"); + var d = F2($"{1}", $"2", $"{3}"); + """; + var comp = CreateCompilation( + [source1, testMethods, customHandler, CollectionExpressionTests.s_collectionExtensions], + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + + string f1InterpolatedSig = generateMethodSignature("F1", interpolatedType); + string f1StringSig = generateMethodSignature("F1", stringType); + string f2InterpolatedSig = generateMethodSignature("F2", interpolatedType); + string f2StringSig = generateMethodSignature("F2", stringType); + comp.VerifyDiagnostics( + // (1,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(IReadOnlyList)' and 'Program.F1(IReadOnlyCollection)' + // var a = F1(); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(f1StringSig, f1InterpolatedSig).WithLocation(1, 9), + // (2,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(IReadOnlyCollection)' and 'Program.F2(IReadOnlyList)' + // var b = F2(); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(f2InterpolatedSig, f2StringSig).WithLocation(2, 9), + // (3,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(string[])' and 'Program.F1(CustomHandler[])' + // var c = F1($"{1}", $"2", $"{3}"); + Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments(f1StringSig, f1InterpolatedSig).WithLocation(3, 9), + // (4,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(CustomHandler[])' and 'Program.F2(string[])' + // var d = F2($"{1}", $"2", $"{3}"); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments(f2InterpolatedSig, f2StringSig).WithLocation(4, 9) + ); + + var source2 = $$""" + using System; + var c = F1($"{1}", $"{2}", $"{3}"); + Console.WriteLine(c.GetTypeName()); + var d = F2($"{1}", $"{2}", $"{3}"); + Console.WriteLine(d.GetTypeName()); + var e = F1([$"1", $"2", $"3"]); + Console.WriteLine(e.GetTypeName()); + var f = F2([$"1", $"2", $"3"]); + Console.WriteLine(f.GetTypeName()); + """; + + comp = CreateCompilation( + [source2, testMethods, customHandler, CollectionExpressionTests.s_collectionExtensions], + targetFramework: TargetFramework.Net80, + options: TestOptions.ReleaseExe); + + string outputStringType = stringType.Replace("string", "System.String"); + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: ExpectedOutput($""" + {interpolatedType} + {interpolatedType} + {outputStringType} + {outputStringType} + """)); + + static string generateMethod(string methodName, string parameterType) => + $"static System.Type {methodName}(params {parameterType} value) => typeof({parameterType});"; + + static string generateMethodSignature(string methodName, string parameterType) => + $"Program.{methodName}(params {parameterType})"; + } + [Fact] public void BetterConversionFromExpression_02() // This is a clone of a unit-test from CollectionExpressionTests.cs { @@ -5451,25 +5546,11 @@ static void Main() } } """; + + // Both calls are ambiguous for collection expressions due to different betterness among arguments. CreateCompilation( new[] { source, CollectionExpressionTests.s_collectionExtensionsWithSpan }, targetFramework: TargetFramework.Net80).VerifyDiagnostics( - // Inline collection expression works in this case. - // For 'params' case it fails because: - // - For the first argument, 'int[]' and 'Span' -> neither is better - // - For the second argument, 'int' and 'int' -> neither is better vs. 'int[]' and 'ReadOnlySpan' -> ReadOnlySpan for a collection expression - // Parameters type sequences are different, tie-breaking rules do not apply. - - // 0.cs(10,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F1(int[], params int[])' and 'Program.F1(Span, params ReadOnlySpan)' - // F1([1], 2); - Diagnostic(ErrorCode.ERR_AmbigCall, "F1").WithArguments("Program.F1(int[], params int[])", "Program.F1(System.Span, params System.ReadOnlySpan)").WithLocation(10, 9), - - // Inline collection expression works in this case. - // For 'params' case it fails because: - // - For the first argument, 'object' and 'string' -> string - // - For the second argument, 'string' and 'object' -> string (different direction) vs. 'string[]' and 'Span' -> neither is better for a collection expression - // Parameters type sequences are different, tie-breaking rules do not apply. - // 0.cs(11,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(object, params string[])' and 'Program.F2(string, params Span)' // F2("3", "4"); Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(object, params string[])", "Program.F2(string, params System.Span)").WithLocation(11, 9) @@ -5642,6 +5723,95 @@ static void Main() CompileAndVerify(source, expectedOutput: "string[]"); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/73857")] + public void BetterConversionFromExpression_08C() // This is a clone of a unit-test from CollectionExpressionTests.cs + { + string source = """ + using System; + class Program + { + static void F2(params ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void F2(params ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void Main() + { + F2("a", "b"); + } + } + """; + CompileAndVerify(source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: ExpectedOutput("ReadOnlySpan")); + } + + [Fact] + public void BetterConversionFromExpression_09() // This is a clone of a unit-test from CollectionExpressionTests.cs + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void F2(params List value) { Console.WriteLine("List"); } + static void F2(params List value) { Console.WriteLine("List"); } + static void Main() + { + F2(1, (byte)2); + } + } + """; + + CreateCompilation(source).VerifyDiagnostics( + // (9,9): error CS0121: The call is ambiguous between the following methods or properties: 'Program.F2(List)' and 'Program.F2(List)' + // F2([1, (byte)2]); + Diagnostic(ErrorCode.ERR_AmbigCall, "F2").WithArguments("Program.F2(params System.Collections.Generic.List)", "Program.F2(params System.Collections.Generic.List)").WithLocation(9, 9)); + } + + [Fact] + public void BetterConversionFromExpression_10() // This is a clone of a unit-test from CollectionExpressionTests.cs + { + string source = """ + using System; + using System.Collections.Generic; + class Program + { + static void F2(params List value) { Console.WriteLine("List"); } + static void F2(params List value) { Console.WriteLine("List"); } + static void Main() + { + F2((byte)1, (byte)2); + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13, expectedOutput: "List"); + } + + [Theory] + [InlineData("System.FormattableString")] + [InlineData("System.IFormattable")] + public void BetterConversionFromExpression_11(string formatType) // This is a clone of a unit-test from CollectionExpressionTests.cs + { + string source = $$""" + using System; + class Program + { + static void F2(params ReadOnlySpan value) { Console.WriteLine("ReadOnlySpan"); } + static void F2(params ReadOnlySpan<{{formatType}}> value) { Console.WriteLine("ReadOnlySpan<{{formatType}}>"); } + static void Main() + { + F2($"{1}"); + } + } + """; + + CompileAndVerify(source, + parseOptions: TestOptions.Regular13, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: ExpectedOutput("ReadOnlySpan")); + } + [Theory] [InlineData("System.ReadOnlySpan")] [InlineData("System.Span")]