diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index 4a298445c97db..6b44fcc3caba8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -245,6 +245,7 @@ private static void AssertTrivialConversion(ConversionKind kind) case ConversionKind.InterpolatedStringHandler: case ConversionKind.InlineArray: case ConversionKind.ImplicitSpan: + case ConversionKind.ExplicitSpan: isTrivial = true; break; @@ -296,6 +297,7 @@ internal static Conversion GetTrivialConversion(ConversionKind kind) internal static Conversion FunctionType => new Conversion(ConversionKind.FunctionType); internal static Conversion InlineArray => new Conversion(ConversionKind.InlineArray); internal static Conversion ImplicitSpan => new Conversion(ConversionKind.ImplicitSpan); + internal static Conversion ExplicitSpan => new Conversion(ConversionKind.ExplicitSpan); // trivial conversions that could be underlying in nullable conversion // NOTE: tuple conversions can be underlying as well, but they are not trivial @@ -831,7 +833,7 @@ public bool IsReference { get { - return Kind == ConversionKind.ImplicitSpan; + return Kind is ConversionKind.ImplicitSpan or ConversionKind.ExplicitSpan; } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs index b72eeca287d21..7c7f64d3765d8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKind.cs @@ -70,5 +70,6 @@ internal enum ConversionKind : byte InlineArray, // A conversion from an inline array to Span/ReadOnlySpan ImplicitSpan, // A conversion between array, (ReadOnly)Span, string - part of the "first-class Span types" feature + ExplicitSpan, // A conversion from array to (ReadOnly)Span } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs index 10a6f6c44b291..b25b4bef8aaa6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionKindExtensions.cs @@ -69,6 +69,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind) case ExplicitPointerToInteger: case ExplicitIntegerToPointer: case IntPtr: + case ExplicitSpan: return false; default: diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index a3ca8288b09f1..2dc25bd5a75fb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -848,6 +848,11 @@ private Conversion ClassifyExplicitBuiltInOnlyConversion(TypeSymbol source, Type return Conversion.ExplicitDynamic; } + if (HasExplicitSpanConversion(source, destination, ref useSiteInfo)) + { + return Conversion.ExplicitSpan; + } + return Conversion.NoConversion; } @@ -995,6 +1000,7 @@ private static bool ExplicitConversionMayDifferFromImplicit(Conversion implicitC case ConversionKind.ImplicitTupleLiteral: case ConversionKind.ImplicitNullable: case ConversionKind.ConditionalExpression: + case ConversionKind.ImplicitSpan: return true; default: @@ -1903,7 +1909,7 @@ public Conversion ConvertExtensionMethodThisArg(TypeSymbol parameterType, TypeSy public Conversion ClassifyImplicitExtensionMethodThisArgConversion(BoundExpression sourceExpressionOpt, TypeSymbol sourceType, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { Debug.Assert(sourceExpressionOpt is null || Compilation is not null); - Debug.Assert(sourceExpressionOpt == null || (object)sourceExpressionOpt.Type == sourceType); + Debug.Assert(sourceExpressionOpt == null || TypeSymbol.Equals(sourceExpressionOpt.Type, sourceType, TypeCompareKind.ConsiderEverything)); Debug.Assert((object)destination != null); if ((object)sourceType != null) @@ -3921,7 +3927,8 @@ private static bool IsIntegerTypeSupportingPointerConversions(TypeSymbol type) return false; } - private bool HasImplicitSpanConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) +#nullable enable + private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { // PROTOTYPE: Is it fine that this conversion does not exists when Compilation is null? if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != true) @@ -3961,5 +3968,61 @@ bool hasIdentityConversion(TypeWithAnnotations source, TypeWithAnnotations desti HasTopLevelNullabilityIdentityConversion(source, destination); } } + + /// + /// This does not check implicit span conversions, that should be done by the caller. + /// + private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) + { + // PROTOTYPE: Is it fine that this conversion does not exists when Compilation is null? + if (Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != true) + { + return false; + } + + // SPEC: From any single-dimensional `array_type` with element type `Ti` + // to `System.Span` or `System.ReadOnlySpan` + // provided an explicit reference conversion exists from `Ti` to `Ui`. + if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType } && + (destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions) || + destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))) + { + var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; + return HasIdentityOrReferenceConversion(elementType.Type, spanElementType.Type, ref useSiteInfo) && + HasTopLevelNullabilityIdentityConversion(elementType, spanElementType); + } + + return false; + } + + private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? target) + { + if (source is null || target is null) + { + return false; + } + + // PROTOTYPE: Is it fine that this check is not performed when Compilation is null? + return Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) == true && + (ignoreUserDefinedSpanConversionsInOneDirection(Compilation, source, target) || + ignoreUserDefinedSpanConversionsInOneDirection(Compilation, target, source)); + + static bool ignoreUserDefinedSpanConversionsInOneDirection(CSharpCompilation compilation, TypeSymbol a, TypeSymbol b) + { + // SPEC: User-defined conversions are not considered when converting between + // SPEC: - any single-dimensional `array_type` and `System.Span`/`System.ReadOnlySpan` + if (a is ArrayTypeSymbol { IsSZArray: true } && + (b.OriginalDefinition.Equals(compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions) || + b.OriginalDefinition.Equals(compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))) + { + return true; + } + + // PROTOTYPE: - any combination of `System.Span`/`System.ReadOnlySpan` + // PROTOTYPE: - `string` and `System.ReadOnlySpan` + + return false; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs index bc402d211e3f0..c5b820017cb57 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs @@ -212,6 +212,11 @@ private void AddUserDefinedConversionsToExplicitCandidateSet( return; } + if (IgnoreUserDefinedSpanConversions(source, target)) + { + return; + } + ImmutableArray operators = declaringType.GetOperators( isExplicit ? (isChecked ? WellKnownMemberNames.CheckedExplicitConversionName : WellKnownMemberNames.ExplicitConversionName) : WellKnownMemberNames.ImplicitConversionName); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs index d92bc7d996754..bef9cbb923111 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs @@ -245,6 +245,11 @@ private void ComputeApplicableUserDefinedImplicitConversionSet( return; } + if (IgnoreUserDefinedSpanConversions(source, target)) + { + return; + } + bool haveInterfaces = false; foreach ((NamedTypeSymbol declaringType, TypeParameterSymbol constrainedToTypeOpt) in d) @@ -639,6 +644,7 @@ private static bool IsEncompassingImplicitConversionKind(ConversionKind kind) case ConversionKind.IntPtr: case ConversionKind.ExplicitTupleLiteral: case ConversionKind.ExplicitTuple: + case ConversionKind.ExplicitSpan: return false; // Spec'd in C# 4. diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 09bcd2e580f8e..679f989d463a6 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -7908,10 +7908,10 @@ private Conversion GenerateConversionForConditionalOperator(BoundExpression sour return conversion; } - private Conversion GenerateConversion(Conversions conversions, BoundExpression? sourceExpression, TypeSymbol? sourceType, TypeSymbol destinationType, bool fromExplicitCast, bool extensionMethodThisArgument, bool isChecked) + private Conversion GenerateConversion(Conversions conversions, BoundExpression? sourceExpression, TypeSymbol? sourceType, TypeSymbol destinationType, bool fromExplicitCast, bool extensionMethodThisArgument, bool isChecked, bool forceFromExpression = false) { var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - bool useExpression = sourceType is null || UseExpressionForConversion(sourceExpression); + bool useExpression = forceFromExpression || sourceType is null || UseExpressionForConversion(sourceExpression); if (extensionMethodThisArgument) { return conversions.ClassifyImplicitExtensionMethodThisArgConversion( @@ -8890,7 +8890,6 @@ private TypeWithState VisitConversion( break; case ConversionKind.InlineArray: - case ConversionKind.ImplicitSpan: if (checkConversion) { conversion = GenerateConversion(_conversions, conversionOperand, operandType.Type, targetType, fromExplicitCast, extensionMethodThisArgument, isChecked: conversionOpt?.Checked ?? false); @@ -8898,6 +8897,20 @@ private TypeWithState VisitConversion( } break; + case ConversionKind.ImplicitSpan: + case ConversionKind.ExplicitSpan: + if (checkConversion) + { + var previousKind = conversion.Kind; + conversion = GenerateConversion(_conversions, conversionOperand, operandType.Type, targetType, fromExplicitCast, extensionMethodThisArgument, isChecked: conversionOpt?.Checked ?? false, + // Span conversion is "from expression". + // PROTOTYPE: Should it be "from type" instead? + forceFromExpression: true); + Debug.Assert(!conversion.Exists || conversion.Kind == previousKind); + canConvertNestedNullability = conversion.Exists; + } + break; + default: Debug.Assert(targetType.IsValueType || targetType.IsErrorType()); break; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 68148b34f2678..19bc054cbcc5f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -615,6 +615,7 @@ private BoundExpression MakeConversionNodeCore( } case ConversionKind.ImplicitSpan: + case ConversionKind.ExplicitSpan: { var spanType = (NamedTypeSymbol)rewrittenType; diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 1e4db5c5db075..64f6e08682ca6 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -63,27 +63,39 @@ readonly struct StringValues // public static string Join(string separator, params ReadOnlySpan values) => "span"; Diagnostic(ErrorCode.ERR_FeatureInPreview, "params ReadOnlySpan values").WithArguments("params collections").WithLocation(13, 49)); - var expectedOutput = "array"; + var expectedDiagnostics = new[] + { + // (7,65): error CS0121: The call is ambiguous between the following methods or properties: 'StringExtensions.Join(string, params string[])' and 'StringExtensions.Join(string, params ReadOnlySpan)' + // public static string M(StringValues sv) => StringExtensions.Join(",", sv); + Diagnostic(ErrorCode.ERR_AmbigCall, "Join").WithArguments("StringExtensions.Join(string, params string[])", "StringExtensions.Join(string, params System.ReadOnlySpan)").WithLocation(7, 65) + }; - var expectedIl = """ - { - // Code size 17 (0x11) - .maxstack 2 - IL_0000: ldstr "," - IL_0005: ldarg.0 - IL_0006: call "string[] StringValues.op_Implicit(StringValues)" - IL_000b: call "string StringExtensions.Join(string, params string[])" - IL_0010: ret - } - """; + CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpan(source).VerifyDiagnostics(expectedDiagnostics); - var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext); - var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); - verifier.VerifyIL("C.M", expectedIl); + // PROTOTYPE: Need to consider "implicit span conversion" in "better conversion target" for this to work. - comp = CreateCompilationWithSpan(source); - verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); - verifier.VerifyIL("C.M", expectedIl); + //var expectedOutput = "array"; + + //var expectedIl = """ + // { + // // Code size 17 (0x11) + // .maxstack 2 + // IL_0000: ldstr "," + // IL_0005: ldarg.0 + // IL_0006: call "string[] StringValues.op_Implicit(StringValues)" + // IL_000b: call "string StringExtensions.Join(string, params string[])" + // IL_0010: ret + // } + // """; + + //var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext); + //var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + //verifier.VerifyIL("C.M", expectedIl); + + //comp = CreateCompilationWithSpan(source); + //verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + //verifier.VerifyIL("C.M", expectedIl); } [Fact] @@ -497,7 +509,7 @@ class C var op = (IConversionOperation)model.GetOperation(cast)!; var conv = op.GetConversion(); - Assert.Equal(ConversionKind.ImplicitSpan, conv.Kind); + Assert.Equal(ConversionKind.ExplicitSpan, conv.Kind); model.VerifyOperationTree(cast, """ IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.ReadOnlySpan) (Syntax: '(ReadOnlySpan)x') @@ -513,16 +525,13 @@ class C var op = (IConversionOperation)model.GetOperation(cast)!; var conv = op.GetConversion(); - Assert.Equal(ConversionKind.ExplicitUserDefined, conv.Kind); + Assert.Equal(ConversionKind.ExplicitSpan, conv.Kind); model.VerifyOperationTree(cast, """ - IConversionOperation (TryCast: False, Unchecked) (OperatorMethod: System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(U[] array)) (OperationKind.Conversion, Type: System.ReadOnlySpan) (Syntax: '(ReadOnlySpan)x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: True) (MethodSymbol: System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(U[] array)) + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.ReadOnlySpan) (Syntax: '(ReadOnlySpan)x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: U[], IsImplicit) (Syntax: 'x') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - Operand: - IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') """); } } @@ -542,7 +551,6 @@ class C Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.Span").WithLocation(3, 41)); } - // PROTOTYPE: Revisit all nullable analysis tests. [Fact] public void Conversion_Array_Span_Implicit_NullableAnalysis() { @@ -582,12 +590,21 @@ class C // (6,39): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'Span'. // Span M2(string?[] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.Span").WithLocation(6, 39), + // (7,39): warning CS8619: Nullability of reference types in value of type 'string[]' doesn't match target type 'Span'. + // Span M3(string[] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string[]", "System.Span").WithLocation(7, 39), + // (10,39): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'Span'. + // Span M5(string?[] arg) => (Span)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("string?[]", "System.Span").WithLocation(10, 39), // (11,39): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'Span'. // Span M6(string?[] arg) => (Span)arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("System.Span", "System.Span").WithLocation(11, 39), // (12,39): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'Span'. // Span M7(string[] arg) => (Span)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("System.Span", "System.Span").WithLocation(12, 39) + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("System.Span", "System.Span").WithLocation(12, 39), + // (13,39): warning CS8619: Nullability of reference types in value of type 'string[]' doesn't match target type 'Span'. + // Span M8(string[] arg) => (Span)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("string[]", "System.Span").WithLocation(13, 39) }; CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); @@ -606,11 +623,10 @@ class C ReadOnlySpan M2(string?[] arg) => arg; ReadOnlySpan M3(string[] arg) => arg; ReadOnlySpan M4(string?[] arg) => arg; - ReadOnlySpan M5(string?[] arg) => arg; - ReadOnlySpan M6(string?[] arg) => arg; + ReadOnlySpan M5(string?[] arg) => arg; - ReadOnlySpan M7(string?[] arg) => (ReadOnlySpan)arg; - ReadOnlySpan M8(object?[] arg) => (ReadOnlySpan)arg; + ReadOnlySpan M6(string?[] arg) => (ReadOnlySpan)arg; + ReadOnlySpan M7(object?[] arg) => (ReadOnlySpan)arg; } """; @@ -618,33 +634,30 @@ class C // (6,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'string[]'. // ReadOnlySpan M2(string?[] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "string[]").WithLocation(6, 47), - // (9,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'string[]'. - // ReadOnlySpan M5(string?[] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "string[]").WithLocation(9, 47), - // (10,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'object[]'. - // ReadOnlySpan M6(string?[] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "object[]").WithLocation(10, 47), - // (12,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'string[]'. - // ReadOnlySpan M7(string?[] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[]", "string[]").WithLocation(12, 47), - // (13,47): warning CS8619: Nullability of reference types in value of type 'object?[]' doesn't match target type 'string[]'. - // ReadOnlySpan M8(object?[] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "string[]").WithLocation(13, 47)); + // (9,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'object[]'. + // ReadOnlySpan M5(string?[] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "object[]").WithLocation(9, 47), + // (11,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'string[]'. + // ReadOnlySpan M6(string?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[]", "string[]").WithLocation(11, 47), + // (12,47): warning CS8619: Nullability of reference types in value of type 'object?[]' doesn't match target type 'string[]'. + // ReadOnlySpan M7(object?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "string[]").WithLocation(12, 47)); var expectedDiagnostics = new[] { // (6,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. // ReadOnlySpan M2(string?[] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(6, 47), - // (9,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. - // ReadOnlySpan M5(string?[] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(9, 47), - // (10,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. - // ReadOnlySpan M6(string?[] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(10, 47), - // (13,47): warning CS8619: Nullability of reference types in value of type 'object?[]' doesn't match target type 'string[]'. - // ReadOnlySpan M8(object?[] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "string[]").WithLocation(13, 47) + // (9,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M5(string?[] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(9, 47), + // (11,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(string?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(11, 47), + // (12,47): warning CS8619: Nullability of reference types in value of type 'object?[]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M7(object?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "System.ReadOnlySpan").WithLocation(12, 47) }; CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); @@ -680,7 +693,13 @@ class C { // (6,43): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'Span'. // Span M2(string?[][] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.Span").WithLocation(6, 43) + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.Span").WithLocation(6, 43), + // (7,43): warning CS8619: Nullability of reference types in value of type 'string[][]' doesn't match target type 'Span'. + // Span M3(string[][] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string[][]", "System.Span").WithLocation(7, 43), + // (10,43): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'Span'. + // Span M5(string?[][] arg) => (Span)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(Span)arg").WithArguments("string?[][]", "System.Span").WithLocation(10, 43) }; CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); @@ -699,11 +718,10 @@ class C ReadOnlySpan M2(string?[][] arg) => arg; ReadOnlySpan M3(string[][] arg) => arg; ReadOnlySpan M4(string?[][] arg) => arg; - ReadOnlySpan M5(string?[][] arg) => arg; - ReadOnlySpan M6(string?[][] arg) => arg; + ReadOnlySpan M5(string?[][] arg) => arg; - ReadOnlySpan M7(string?[][] arg) => (ReadOnlySpan)arg; - ReadOnlySpan M8(object?[][] arg) => (ReadOnlySpan)arg; + ReadOnlySpan M6(string?[][] arg) => (ReadOnlySpan)arg; + ReadOnlySpan M7(object?[][] arg) => (ReadOnlySpan)arg; } """; @@ -711,33 +729,27 @@ class C // (6,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'string[][]'. // ReadOnlySpan M2(string?[][] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "string[][]").WithLocation(6, 51), - // (9,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'string[][]'. - // ReadOnlySpan M5(string?[][] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "string[][]").WithLocation(9, 51), - // (10,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'object[][]'. - // ReadOnlySpan M6(string?[][] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "object[][]").WithLocation(10, 51), - // (12,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'string[][]'. - // ReadOnlySpan M7(string?[][] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[][]", "string[][]").WithLocation(12, 51), - // (13,51): warning CS8619: Nullability of reference types in value of type 'object?[][]' doesn't match target type 'string[][]'. - // ReadOnlySpan M8(object?[][] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[][]", "string[][]").WithLocation(13, 51)); + // (9,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'object[][]'. + // ReadOnlySpan M5(string?[][] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "object[][]").WithLocation(9, 51), + // (11,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'string[][]'. + // ReadOnlySpan M6(string?[][] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[][]", "string[][]").WithLocation(11, 51), + // (12,51): warning CS8619: Nullability of reference types in value of type 'object?[][]' doesn't match target type 'string[][]'. + // ReadOnlySpan M7(object?[][] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[][]", "string[][]").WithLocation(12, 51)); var expectedDiagnostics = new[] { // (6,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'ReadOnlySpan'. // ReadOnlySpan M2(string?[][] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.ReadOnlySpan").WithLocation(6, 51), - // (9,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'ReadOnlySpan'. - // ReadOnlySpan M5(string?[][] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.ReadOnlySpan").WithLocation(9, 51), - // (10,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'ReadOnlySpan'. - // ReadOnlySpan M6(string?[][] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.ReadOnlySpan").WithLocation(10, 51), - // (13,51): warning CS8619: Nullability of reference types in value of type 'object?[][]' doesn't match target type 'string[][]'. - // ReadOnlySpan M8(object?[][] arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[][]", "string[][]").WithLocation(13, 51) + // (9,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M5(string?[][] arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("string?[][]", "System.ReadOnlySpan").WithLocation(9, 51), + // (11,51): warning CS8619: Nullability of reference types in value of type 'string?[][]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(string?[][] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[][]", "System.ReadOnlySpan").WithLocation(11, 51) }; CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); @@ -788,11 +800,10 @@ class C ReadOnlySpan> M2(S[] arg) => arg; ReadOnlySpan> M3(S[] arg) => arg; ReadOnlySpan> M4(S[] arg) => arg; - ReadOnlySpan> M5(S[] arg) => arg; - ReadOnlySpan> M6(S[] arg) => arg; + ReadOnlySpan> M5(S[] arg) => arg; - ReadOnlySpan> M7(S[] arg) => (ReadOnlySpan>)arg; - ReadOnlySpan> M8(S[] arg) => (ReadOnlySpan>)arg; + ReadOnlySpan> M6(S[] arg) => (ReadOnlySpan>)arg; + ReadOnlySpan> M7(S[] arg) => (ReadOnlySpan>)arg; } struct S { } """; @@ -803,18 +814,15 @@ struct S { } // (7,53): warning CS8619: Nullability of reference types in value of type 'S[]' doesn't match target type 'S[]'. // ReadOnlySpan> M3(S[] arg) => arg; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("S[]", targetType("string?")).WithLocation(7, 53), - // (9,53): warning CS8619: Nullability of reference types in value of type 'S[]' doesn't match target type 'S[]'. - // ReadOnlySpan> M5(S[] arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("S[]", targetType("string")).WithLocation(9, 53), - // (10,53): error CS0029: Cannot implicitly convert type 'S[]' to 'System.ReadOnlySpan>' - // ReadOnlySpan> M6(S[] arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("S[]", "System.ReadOnlySpan>").WithLocation(10, 53), - // (12,53): warning CS8619: Nullability of reference types in value of type 'S[]' doesn't match target type 'S[]'. - // ReadOnlySpan> M7(S[] arg) => (ReadOnlySpan>)arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan>)arg").WithArguments("S[]", targetType("string")).WithLocation(12, 53), - // (13,53): error CS0030: Cannot convert type 'S[]' to 'System.ReadOnlySpan>' - // ReadOnlySpan> M8(S[] arg) => (ReadOnlySpan>)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)arg").WithArguments("S[]", "System.ReadOnlySpan>").WithLocation(13, 53)); + // (9,53): error CS0029: Cannot implicitly convert type 'S[]' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M5(S[] arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("S[]", "System.ReadOnlySpan>").WithLocation(9, 53), + // (11,53): warning CS8619: Nullability of reference types in value of type 'S[]' doesn't match target type 'S[]'. + // ReadOnlySpan> M6(S[] arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan>)arg").WithArguments("S[]", targetType("string")).WithLocation(11, 53), + // (12,53): error CS0030: Cannot convert type 'S[]' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M7(S[] arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)arg").WithArguments("S[]", "System.ReadOnlySpan>").WithLocation(12, 53)); string targetType(string inner) => langVersion > LanguageVersion.CSharp12 ? $"System.ReadOnlySpan>" : $"S<{inner}>[]"; @@ -1117,8 +1125,8 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string[])arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 54)); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Opposite_Explicit_UserDefined(LanguageVersion langVersion) + [Fact] + public void Conversion_Array_Span_Opposite_Explicit_UserDefined() { var source = """ class C @@ -1134,7 +1142,7 @@ readonly ref struct Span } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular12); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -1145,6 +1153,16 @@ .maxstack 1 IL_0006: ret } """); + + var expectedDiagnostics = new[] + { + // (3,39): error CS0030: Cannot convert type 'System.Span' to 'int[]' + // int[] M(System.Span arg) => (int[])arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int[])arg").WithArguments("System.Span", "int[]").WithLocation(3, 39) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Fact] @@ -1211,27 +1229,30 @@ void M3(params object[] p) { } var verifier = CompileAndVerify(comp).VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { - // Code size 38 (0x26) - .maxstack 2 + // Code size 45 (0x2d) + .maxstack 5 .locals init (object[] V_0) IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: stloc.0 - IL_0003: ldloc.0 - IL_0004: call "System.Span System.Span.op_Implicit(object[])" - IL_0009: call "void C.M1(params System.Span)" - IL_000e: ldarg.0 - IL_000f: ldarg.1 - IL_0010: stloc.0 - IL_0011: ldloc.0 - IL_0012: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(object[])" - IL_0017: call "void C.M2(params System.ReadOnlySpan)" - IL_001c: ldarg.0 - IL_001d: ldarg.1 - IL_001e: stloc.0 - IL_001f: ldloc.0 - IL_0020: call "void C.M3(params object[])" - IL_0025: ret + IL_0001: ldc.i4.1 + IL_0002: newarr "object" + IL_0007: dup + IL_0008: ldc.i4.0 + IL_0009: ldarg.1 + IL_000a: stelem.ref + IL_000b: newobj "System.Span..ctor(object[])" + IL_0010: call "void C.M1(params System.Span)" + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: stloc.0 + IL_0018: ldloc.0 + IL_0019: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(object[])" + IL_001e: call "void C.M2(params System.ReadOnlySpan)" + IL_0023: ldarg.0 + IL_0024: ldarg.1 + IL_0025: stloc.0 + IL_0026: ldloc.0 + IL_0027: call "void C.M3(params object[])" + IL_002c: ret } """); } @@ -1652,8 +1673,8 @@ interface I<{{variance}} T> where T : allows ref struct { } Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("I", "I>").WithLocation(5, 49)); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_ReadOnlySpan_Interface_Invariant(LanguageVersion langVersion) + [Fact] + public void Conversion_Array_ReadOnlySpan_Interface_Invariant() { var source = """ using System; @@ -1665,10 +1686,56 @@ class C interface I { } """; - CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular12).VerifyDiagnostics( // (5,49): error CS0029: Cannot implicitly convert type 'I[]' to 'System.ReadOnlySpan>' // ReadOnlySpan> M(I[] x) => x; Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("I[]", "System.ReadOnlySpan>").WithLocation(5, 49)); + + var expectedDiagnostics = new[] + { + // (5,49): error CS0266: Cannot implicitly convert type 'I[]' to 'System.ReadOnlySpan>'. An explicit conversion exists (are you missing a cast?) + // ReadOnlySpan> M(I[] x) => x; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("I[]", "System.ReadOnlySpan>").WithLocation(5, 49) + }; + + CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpan(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_Array_ReadOnlySpan_Interface_Cast() + { + var source = """ + using System; + + class C + { + ReadOnlySpan> M(I[] x) => (ReadOnlySpan>)x; + } + + interface I { } + """; + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular12).VerifyDiagnostics( + // (5,49): error CS0030: Cannot convert type 'I[]' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M(I[] x) => (ReadOnlySpan>)x; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)x").WithArguments("I[]", "System.ReadOnlySpan>").WithLocation(5, 49)); + + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpan(source); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + verifier.VerifyIL("C.M", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: castclass "I[]" + IL_0006: call "System.ReadOnlySpan> System.ReadOnlySpan>.op_Implicit(I[])" + IL_000b: ret + } + """); } [Theory, MemberData(nameof(LangVersions))] @@ -1804,10 +1871,10 @@ class C where T : struct Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("T[]", "System.ReadOnlySpan").WithLocation(5, 34)); } - [Theory] - [InlineData("")] - [InlineData("where U : T")] - public void Conversion_Array_Span_Variance_01(string constraints) + [Theory, CombinatorialData] + public void Conversion_Array_Span_Variance_01( + [CombinatorialValues("", "where U : T")] string constraints, + [CombinatorialLangVersions] LanguageVersion langVersion) { var source = $$""" using System; @@ -1819,7 +1886,7 @@ class C {{constraints}} Span F4(T[] x) => (Span)x; } """; - CreateCompilationWithSpan(source).VerifyDiagnostics( + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( // (4,34): error CS0029: Cannot implicitly convert type 'T[]' to 'System.ReadOnlySpan' // ReadOnlySpan F1(T[] x) => x; Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("T[]", "System.ReadOnlySpan").WithLocation(4, 34), @@ -1834,9 +1901,8 @@ class C {{constraints}} Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)x").WithArguments("T[]", "System.Span").WithLocation(7, 26)); } - // PROTOTYPE: User-defined conversions should not be considered? Note: this is not the only test affected. In particular, nullable analysis tests should be revisited. - [Fact] - public void Conversion_Array_Span_Variance_02() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Variance_02(LanguageVersion langVersion) { var source = """ using System; @@ -1850,7 +1916,7 @@ class C Span F4(T[] x) => (Span)x; } """; - CreateCompilationWithSpan(source).VerifyDiagnostics( + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( // (6,34): error CS0266: Cannot implicitly convert type 'T[]' to 'System.ReadOnlySpan'. An explicit conversion exists (are you missing a cast?) // ReadOnlySpan F1(T[] x) => x; Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("T[]", "System.ReadOnlySpan").WithLocation(6, 34), @@ -1874,7 +1940,20 @@ class C Span F4(T[] x) => (Span)x; } """; - CreateCompilationWithSpan(source).VerifyDiagnostics(); + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular12).VerifyDiagnostics(); + + // Note: although a breaking change, the previous would fail with a runtime exception + // (Span's constructor checks that the element types are identical). + + var expectedDiagnostics = new[] + { + // (8,26): error CS0266: Cannot implicitly convert type 'T[]' to 'System.Span'. An explicit conversion exists (are you missing a cast?) + // Span F3(T[] x) => x; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("T[]", "System.Span").WithLocation(8, 26) + }; + + CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpan(source).VerifyDiagnostics(expectedDiagnostics); } [Theory, CombinatorialData] @@ -1917,6 +1996,78 @@ public static void M({{destination}} xs) CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } + [Theory, CombinatorialData] + public void Conversion_Array_Span_ThroughUserImplicit_Cast_01( + [CombinatorialValues("Span", "ReadOnlySpan")] string destination, + [CombinatorialValues("implicit", "explicit")] string op) + { + var source = $$""" + using System; + + D.M(({{destination}})new C()); + + class C + { + public static {{op}} operator int[](C c) => new int[] { 4, 5, 6 }; + } + + static class D + { + public static void M({{destination}} xs) + { + foreach (var x in xs) + { + Console.Write(x); + } + } + } + """; + + CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular12).VerifyDiagnostics( + // (3,5): error CS0030: Cannot convert type 'C' to 'System.Span' + // D.M((Span)new C()); + Diagnostic(ErrorCode.ERR_NoExplicitConv, $"({destination})new C()").WithArguments("C", $"System.{destination}").WithLocation(3, 5)); + + var expectedOutput = "456"; + + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpan(source); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Theory, CombinatorialData] + public void Conversion_Array_Span_ThroughUserImplicit_Cast_02( + [CombinatorialValues("Span", "ReadOnlySpan")] string destination, + [CombinatorialValues("implicit", "explicit")] string op, + [CombinatorialLangVersions] LanguageVersion langVersion) + { + var source = $$""" + using System; + + D.M((int[])new C()); + + class C + { + public static {{op}} operator int[](C c) => new int[] { 4, 5, 6 }; + } + + static class D + { + public static void M({{destination}} xs) + { + foreach (var x in xs) + { + Console.Write(x); + } + } + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + CompileAndVerify(comp, expectedOutput: "456").VerifyDiagnostics(); + } + [Fact] public void Conversion_Array_Span_ThroughUserImplicit_MissingHelper() { @@ -2396,8 +2547,8 @@ static void E3(this string[] arg) { } Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string[])arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 57)); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_ExtensionMethodReceiver_Opposite_Explicit_UserDefined(LanguageVersion langVersion) + [Fact] + public void Conversion_Array_Span_ExtensionMethodReceiver_Opposite_Explicit_UserDefined() { var source = """ static class C @@ -2414,7 +2565,7 @@ readonly ref struct Span } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular12); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -2426,6 +2577,19 @@ .maxstack 1 IL_000b: ret } """); + + // Note: although a breaking change, the previous would fail with a runtime exception + // (Span's constructor checks that the element types are identical). + + var expectedDiagnostics = new[] + { + // (3,45): error CS0030: Cannot convert type 'System.Span' to 'int[]' + // static void M(System.Span arg) => ((int[])arg).E(); + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(int[])arg").WithArguments("System.Span", "int[]").WithLocation(3, 45) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Theory] @@ -2710,6 +2874,40 @@ static class C CompileAndVerify(comp, expectedOutput: "aa rSystem.Object[] ra ra").VerifyDiagnostics(); } + [Fact] + public void OverloadResolution_ReadOnlySpanVsArray_05() + { + var source = """ + using System; + + C.M(null); + C.M(default); + + static class C + { + public static void M(object[] x) => Console.Write(1); + public static void M(ReadOnlySpan x) => Console.Write(2); + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular12); + CompileAndVerify(comp, expectedOutput: "11").VerifyDiagnostics(); + + // PROTOTYPE: Need to consider "implicit span conversion" in "better conversion target" for this to work. + + var expectedDiagnostics = new[] + { + // (3,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(object[])' and 'C.M(ReadOnlySpan)' + // C.M(null); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(object[])", "C.M(System.ReadOnlySpan)").WithLocation(3, 3), + // (4,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(object[])' and 'C.M(ReadOnlySpan)' + // C.M(default); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(object[])", "C.M(System.ReadOnlySpan)").WithLocation(4, 3) + }; + + CreateCompilationWithSpan(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpan(source).VerifyDiagnostics(expectedDiagnostics); + } + [Fact] public void OverloadResolution_ReadOnlySpanVsArray_Params_01() { @@ -2803,6 +3001,33 @@ static class C CompileAndVerify(comp, expectedOutput: "aa rSystem.Object[] ra ra").VerifyDiagnostics(); } + [Fact] + public void OverloadResolution_ReadOnlySpanVsArray_Params_05() + { + var source = """ + using System; + + C.M(null); + C.M(default); + + static class C + { + public static void M(params object[] x) => Console.Write(1); + public static void M(params ReadOnlySpan x) => Console.Write(2); + } + """; + + // PROTOTYPE: Need to consider "implicit span conversion" in "better conversion target" for this to work. + + CreateCompilationWithSpan(source).VerifyDiagnostics( + // (3,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(params object[])' and 'C.M(params ReadOnlySpan)' + // C.M(null); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(params object[])", "C.M(params System.ReadOnlySpan)").WithLocation(3, 3), + // (4,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M(params object[])' and 'C.M(params ReadOnlySpan)' + // C.M(default); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M(params object[])", "C.M(params System.ReadOnlySpan)").WithLocation(4, 3)); + } + [Theory, MemberData(nameof(LangVersions))] public void OverloadResolution_ReadOnlySpanVsArray_ExtensionMethodReceiver_01(LanguageVersion langVersion) { @@ -2895,6 +3120,25 @@ static class C CompileAndVerify(comp, expectedOutput: "aa").VerifyDiagnostics(); } + [Theory, MemberData(nameof(LangVersions))] + public void OverloadResolution_ReadOnlySpanVsArray_ExtensionMethodReceiver_05(LanguageVersion langVersion) + { + var source = """ + using System; + + ((object[])null).M(); + default(object[]).M(); + + static class C + { + public static void M(this object[] x) => Console.Write(1); + public static void M(this ReadOnlySpan x) => Console.Write(2); + } + """; + var comp = CreateCompilationWithSpan(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + CompileAndVerify(comp, expectedOutput: "11").VerifyDiagnostics(); + } + [Theory, MemberData(nameof(LangVersions))] public void OverloadResolution_SpanVsReadOnlySpan_01(LanguageVersion langVersion) { diff --git a/src/Workspaces/CoreTest/XxHash128Tests.cs b/src/Workspaces/CoreTest/XxHash128Tests.cs index 755b2a9650e36..df6040457600c 100644 --- a/src/Workspaces/CoreTest/XxHash128Tests.cs +++ b/src/Workspaces/CoreTest/XxHash128Tests.cs @@ -20,7 +20,8 @@ public class XxHash128Tests public void Hash_InvalidInputs_Throws() { Assert.Throws("source", () => XxHash128.Hash(null)); - Assert.Throws("source", () => XxHash128.Hash(null, 42)); + // PROTOTYPE: Should make this work with `null` instead of `default(byte[])`. + Assert.Throws("source", () => XxHash128.Hash(default(byte[]), 42)); Assert.Throws("destination", () => XxHash128.Hash(new byte[] { 1, 2, 3 }, new byte[7])); }