From d751f5f35bfafba0481e38631e7b7c83eb4c6620 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 17 Jul 2024 19:02:39 +0200 Subject: [PATCH 1/8] Implement remaining span conversions --- .../Portable/Binder/Binder_Conversions.cs | 200 +- .../Semantics/Conversions/ConversionsBase.cs | 39 +- .../LocalRewriter/LocalRewriter_Conversion.cs | 87 +- .../CodeGenReadOnlySpanConstructionTest.cs | 10 +- .../CSharp/Test/Emit/CodeGen/ForeachTest.cs | 2 +- .../Test/Emit/CodeGen/IndexAndRangeTests.cs | 8 +- .../Test/Emit2/Semantics/InlineArrayTests.cs | 29 +- .../Emit2/Semantics/PatternMatchingTests.cs | 2 +- .../Semantics/PrimaryConstructorTests.cs | 5 +- .../CSharp/Test/Emit3/FirstClassSpanTests.cs | 3508 +++++++++++++++-- .../RawInterpolationTests_Handler.cs | 16 +- .../Semantics/StructConstructorTests.cs | 23 +- .../Semantics/Utf8StringsLiteralsTests.cs | 8 +- .../Portable/Symbols/WellKnownMemberNames.cs | 4 + .../Test/Utilities/CSharp/CSharpTestBase.cs | 18 + .../Test/Utilities/CSharp/TestSources.cs | 15 +- 16 files changed, 3521 insertions(+), 453 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 9310fe215b6ef..e61ebf6cc4dbb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -487,51 +487,223 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn } else if (conversion.IsSpan) { + Debug.Assert(source.Type is not null); Debug.Assert(destination.OriginalDefinition.IsSpan() || destination.OriginalDefinition.IsReadOnlySpan()); CheckFeatureAvailability(syntax, MessageID.IDS_FeatureFirstClassSpan, diagnostics); - // PROTOTYPE: Check runtime APIs used for other span conversions once they are implemented. // NOTE: We cannot use well-known members because per the spec // the Span types involved in the Span conversions can be any that match the type name. - if (TryFindImplicitOperatorFromArray(destination.OriginalDefinition) is { } method) + + // Span.op_Implicit(T[]) or ReadOnlySpan.op_Implicit(T[]) + if (source.Type is ArrayTypeSymbol) { - diagnostics.ReportUseSite(method, syntax); + reportUseSiteOrMissing( + TryFindImplicitOperatorFromArray(destination.OriginalDefinition), + destination.OriginalDefinition, + WellKnownMemberNames.ImplicitConversionName, + syntax, + diagnostics); } - else + + // ReadOnlySpan Span.op_Implicit(Span) + if (source.Type.OriginalDefinition.IsSpan()) { - Error(diagnostics, - ErrorCode.ERR_MissingPredefinedMember, + Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + reportUseSiteOrMissing( + TryFindImplicitOperatorFromSpan(source.Type.OriginalDefinition, destination.OriginalDefinition), + source.Type.OriginalDefinition, + WellKnownMemberNames.ImplicitConversionName, syntax, - destination.OriginalDefinition, - WellKnownMemberNames.ImplicitConversionName); + diagnostics); + } + + // ReadOnlySpan ReadOnlySpan.CastUp(ReadOnlySpan) + if (source.Type.OriginalDefinition.IsSpan() || source.Type.OriginalDefinition.IsReadOnlySpan()) + { + Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + if (NeedsSpanCastUp(source.Type, destination)) + { + // If converting Span -> ROS -> ROS, + // the source of the CastUp is the return type of the op_Implicit (i.e., the ROS) + // which has the same original definition as the destination ROS. + TypeSymbol sourceForCastUp = source.Type.OriginalDefinition.IsSpan() + ? destination.OriginalDefinition + : source.Type.OriginalDefinition; + + MethodSymbol? castUpMethod = TryFindCastUpMethod(sourceForCastUp.OriginalDefinition, destination.OriginalDefinition); + reportUseSiteOrMissing( + castUpMethod, + destination.OriginalDefinition, + WellKnownMemberNames.CastUpMethodName, + syntax, + diagnostics); + castUpMethod? + .AsMember((NamedTypeSymbol)destination) + .Construct([((NamedTypeSymbol)source.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]]) + .CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(Compilation, Conversions, includeNullability: false, syntax.Location, diagnostics)); + } + } + + // ReadOnlySpan MemoryExtensions.AsSpan(string) + if (source.Type.IsStringType()) + { + reportUseSiteOrMissing( + TryFindAsSpanCharMethod(Compilation, destination), + WellKnownMemberNames.MemoryExtensionsTypeFullName, + WellKnownMemberNames.AsSpanMethodName, + syntax, + diagnostics); } } } + + static void reportUseSiteOrMissing(MethodSymbol? method, object containingType, string methodName, SyntaxNode syntax, BindingDiagnosticBag diagnostics) + { + if (method is not null) + { + diagnostics.ReportUseSite(method, syntax); + } + else + { + Error(diagnostics, + ErrorCode.ERR_MissingPredefinedMember, + syntax, + containingType, + methodName); + } + } } + // {type}.op_Implicit(T[]) internal static MethodSymbol? TryFindImplicitOperatorFromArray(TypeSymbol type) { Debug.Assert(type.IsSpan() || type.IsReadOnlySpan()); + Debug.Assert(type.IsDefinition); - return TryFindSingleMember(type, WellKnownMemberNames.ImplicitConversionName, - static (method) => method is + return TryFindImplicitOperator(type, 0, static (_, parameterType) => + parameterType is ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol }); + } + + // ReadOnlySpan Span.op_Implicit(Span) + internal static MethodSymbol? TryFindImplicitOperatorFromSpan(TypeSymbol spanType, TypeSymbol readonlySpanType) + { + Debug.Assert(spanType.IsSpan() && readonlySpanType.IsReadOnlySpan()); + Debug.Assert(spanType.IsDefinition && readonlySpanType.IsDefinition); + + return TryFindImplicitOperator(spanType, readonlySpanType, + parameterPredicate: static (_, parameterType) => parameterType.IsSpan(), + returnTypePredicate: static (readonlySpanType, returnType) => readonlySpanType.Equals(returnType.OriginalDefinition, TypeCompareKind.ConsiderEverything)); + } + + private static MethodSymbol? TryFindImplicitOperator(TypeSymbol type, TArg arg, + Func parameterPredicate, + Func? returnTypePredicate = null) + { + return TryFindSingleMember(type, WellKnownMemberNames.ImplicitConversionName, (parameterPredicate, returnTypePredicate, arg), + static (arg, method) => method is { ParameterCount: 1, Arity: 0, IsStatic: true, DeclaredAccessibility: Accessibility.Public, - Parameters: [{ Type: ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol } }] - }); + Parameters: [{ Type: { } parameterType }], + } && arg.parameterPredicate(arg.arg, parameterType) && + arg.returnTypePredicate?.Invoke(arg.arg, method.ReturnType) != false); + } + + internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) + { + Debug.Assert(source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(!source.IsDefinition && !destination.IsDefinition); + + var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; + + var sameElementTypes = sourceElementType.Equals(destinationElementType, TypeCompareKind.AllIgnoreOptions); + + if (source.OriginalDefinition.IsReadOnlySpan()) + { + Debug.Assert(!sameElementTypes); + } + else + { + Debug.Assert(source.OriginalDefinition.IsSpan()); + } + + return !sameElementTypes; + } + + // ReadOnlySpan ReadOnlySpan.CastUp(ReadOnlySpan) where TDerived : class + internal static MethodSymbol? TryFindCastUpMethod(TypeSymbol source, TypeSymbol destination) + { + Debug.Assert(source.IsReadOnlySpan() && destination.IsReadOnlySpan()); + Debug.Assert(source.IsDefinition && destination.IsDefinition); + + return TryFindSingleMember(destination, WellKnownMemberNames.CastUpMethodName, (source, destination), + static (arg, method) => method is + { + ParameterCount: 1, + Arity: 1, + IsStatic: true, + DeclaredAccessibility: Accessibility.Public, + Parameters: [{ } parameter], + TypeArgumentsWithAnnotations: [{ } typeArgument], + } && + // parameter type is the source ReadOnlySpan<> + arg.source.Equals(parameter.Type.OriginalDefinition, TypeCompareKind.ConsiderEverything) && + // return type is the destination ReadOnlySpan<> + arg.destination.Equals(method.ReturnType.OriginalDefinition, TypeCompareKind.ConsiderEverything) && + // TDerived : class + typeArgument.Type.IsReferenceType && + // parameter type argument is TDerived + ((NamedTypeSymbol)parameter.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(typeArgument.Type, TypeCompareKind.ConsiderEverything) && + // return type argument is T + ((NamedTypeSymbol)method.ReturnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type.Equals(((NamedTypeSymbol)arg.destination).TypeParameters[0], TypeCompareKind.ConsiderEverything)); + } + + // ReadOnlySpan MemoryExtensions.AsSpan(string) + internal static MethodSymbol? TryFindAsSpanCharMethod(CSharpCompilation compilation, TypeSymbol readOnlySpanType) + { + Debug.Assert(readOnlySpanType.IsReadOnlySpan()); + Debug.Assert(!readOnlySpanType.IsDefinition); + Debug.Assert(((NamedTypeSymbol)readOnlySpanType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].SpecialType is SpecialType.System_Char); + + MethodSymbol? result = null; + foreach (var memoryExtensionsType in compilation.GetTypesByMetadataName(WellKnownMemberNames.MemoryExtensionsTypeFullName)) + { + if (TryFindSingleMember(memoryExtensionsType.GetSymbol(), WellKnownMemberNames.AsSpanMethodName, 0, + static (_, method) => method is + { + ParameterCount: 1, + Arity: 0, + IsStatic: true, + DeclaredAccessibility: Accessibility.Public, + Parameters: [{ Type.SpecialType: SpecialType.System_String }] + }) is { } method && + method.ReturnType.Equals(readOnlySpanType, TypeCompareKind.ConsiderEverything)) + { + if (result is not null) + { + // Ambiguous member found. + return null; + } + + result = method; + } + } + + return result; } - private static MethodSymbol? TryFindSingleMember(TypeSymbol type, string name, Func predicate) + private static MethodSymbol? TryFindSingleMember(TypeSymbol type, string name, TArg arg, Func predicate) { var members = type.GetMembers(name); MethodSymbol? result = null; foreach (var member in members) { - if (member is MethodSymbol method && predicate(method)) + if (member is MethodSymbol method && predicate(arg, method)) { if (result is not null) { diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 51a9c2b7b699b..bca7036b54ccc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -3953,7 +3953,7 @@ private bool IsFeatureFirstClassSpanEnabled private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { - if (!IsFeatureFirstClassSpanEnabled) + if (source is null || !IsFeatureFirstClassSpanEnabled) { return false; } @@ -3975,6 +3975,26 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio return hasCovariantConversion(elementType, spanElementType, ref useSiteInfo); } } + // SPEC: From `System.Span` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible to `Ui`. + // SPEC: From `System.ReadOnlySpan` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible to `Ui`. + else if (source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan()) + { + if (destination.OriginalDefinition.IsReadOnlySpan()) + { + var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; + var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; + return hasCovariantConversion(sourceElementType, destinationElementType, ref useSiteInfo); + } + } + // SPEC: From `string` to `System.ReadOnlySpan`. + else if (source.IsStringType()) + { + if (destination.OriginalDefinition.IsReadOnlySpan()) + { + var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; + return spanElementType.SpecialType is SpecialType.System_Char; + } + } return false; @@ -4019,7 +4039,8 @@ private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? ta { return source is not null && target is not null && IsFeatureFirstClassSpanEnabled && - (ignoreUserDefinedSpanConversionsInOneDirection(source, target) || + (ignoreUserDefinedSpanConversionsInAnyDirection(source, target) || + ignoreUserDefinedSpanConversionsInOneDirection(source, target) || ignoreUserDefinedSpanConversionsInOneDirection(target, source)); static bool ignoreUserDefinedSpanConversionsInOneDirection(TypeSymbol a, TypeSymbol b) @@ -4032,11 +4053,21 @@ static bool ignoreUserDefinedSpanConversionsInOneDirection(TypeSymbol a, TypeSym return true; } - // PROTOTYPE: - any combination of `System.Span`/`System.ReadOnlySpan` - // PROTOTYPE: - `string` and `System.ReadOnlySpan` + // SPEC: - `string` and `System.ReadOnlySpan` + if (a.IsStringType() && b.IsReadOnlySpanChar()) + { + return true; + } return false; } + + static bool ignoreUserDefinedSpanConversionsInAnyDirection(TypeSymbol a, TypeSymbol b) + { + // SPEC: - any combination of `System.Span`/`System.ReadOnlySpan` + return (a.OriginalDefinition.IsSpan() || a.OriginalDefinition.IsReadOnlySpan()) && + (b.OriginalDefinition.IsSpan() || b.OriginalDefinition.IsReadOnlySpan()); + } } } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index b276230485812..fd02ebbb224b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -617,25 +617,98 @@ private BoundExpression MakeConversionNodeCore( case ConversionKind.ImplicitSpan: case ConversionKind.ExplicitSpan: { - var spanType = (NamedTypeSymbol)rewrittenType; + var sourceType = rewrittenOperand.Type; + var destinationType = (NamedTypeSymbol)rewrittenType; - if (Binder.TryFindImplicitOperatorFromArray(spanType.OriginalDefinition) is not { } methodDefinition) + Debug.Assert(sourceType is not null); + + // array to Span/ReadOnlySpan (implicit or explicit) + if (sourceType is ArrayTypeSymbol) { - throw ExceptionUtilities.Unreachable(); + if (Binder.TryFindImplicitOperatorFromArray(destinationType.OriginalDefinition) is not { } methodDefinition) + { + throw ExceptionUtilities.Unreachable(); + } + + MethodSymbol method = methodDefinition.AsMember(destinationType); + + rewrittenOperand = _factory.Convert(method.ParameterTypesWithAnnotations[0].Type, rewrittenOperand); + + if (!_inExpressionLambda && _compilation.IsReadOnlySpanType(destinationType)) + { + return new BoundReadOnlySpanFromArray(syntax, rewrittenOperand, method, destinationType) { WasCompilerGenerated = true }; + } + + return _factory.Call(null, method, rewrittenOperand); } - else + + // Span to ReadOnlySpan (implicit only) + if (sourceType.IsSpan()) { - MethodSymbol method = methodDefinition.AsMember(spanType); + Debug.Assert(destinationType.IsReadOnlySpan()); + Debug.Assert(conversion.Kind is ConversionKind.ImplicitSpan); + + if (Binder.TryFindImplicitOperatorFromSpan(sourceType.OriginalDefinition, destinationType.OriginalDefinition) is not { } implicitOperatorDefinition) + { + throw ExceptionUtilities.Unreachable(); + } + + MethodSymbol implicitOperator = implicitOperatorDefinition.AsMember((NamedTypeSymbol)sourceType); + + rewrittenOperand = _factory.Convert(implicitOperator.ParameterTypesWithAnnotations[0].Type, rewrittenOperand); + rewrittenOperand = _factory.Call(null, implicitOperator, rewrittenOperand); + + if (Binder.NeedsSpanCastUp(sourceType, destinationType)) + { + if (Binder.TryFindCastUpMethod(implicitOperator.ReturnType.OriginalDefinition, destinationType.OriginalDefinition) is not { } castUpMethodDefinition) + { + throw ExceptionUtilities.Unreachable(); + } + + TypeWithAnnotations sourceElementType = ((NamedTypeSymbol)sourceType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; + MethodSymbol castUpMethod = castUpMethodDefinition.AsMember(destinationType).Construct([sourceElementType]); + + return _factory.Call(null, castUpMethod, rewrittenOperand); + } + + return rewrittenOperand; + } + + // ReadOnlySpan to ReadOnlySpan (implicit only) + if (sourceType.IsReadOnlySpan()) + { + Debug.Assert(destinationType.IsReadOnlySpan()); + Debug.Assert(conversion.Kind is ConversionKind.ImplicitSpan); + Debug.Assert(Binder.NeedsSpanCastUp(sourceType, destinationType)); + + if (Binder.TryFindCastUpMethod(sourceType.OriginalDefinition, destinationType.OriginalDefinition) is not { } methodDefinition) + { + throw ExceptionUtilities.Unreachable(); + } + + TypeWithAnnotations sourceElementType = ((NamedTypeSymbol)sourceType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; + MethodSymbol method = methodDefinition.AsMember(destinationType).Construct([sourceElementType]); rewrittenOperand = _factory.Convert(method.ParameterTypesWithAnnotations[0].Type, rewrittenOperand); + return _factory.Call(null, method, rewrittenOperand); + } - if (!_inExpressionLambda && _compilation.IsReadOnlySpanType(spanType)) + // string to ReadOnlySpan (implicit only) + if (sourceType.IsStringType()) + { + Debug.Assert(destinationType.IsReadOnlySpan()); + Debug.Assert(conversion.Kind is ConversionKind.ImplicitSpan); + + if (Binder.TryFindAsSpanCharMethod(_compilation, destinationType) is not { } method) { - return new BoundReadOnlySpanFromArray(syntax, rewrittenOperand, method, spanType) { WasCompilerGenerated = true }; + throw ExceptionUtilities.Unreachable(); } + rewrittenOperand = _factory.Convert(method.ParameterTypesWithAnnotations[0].Type, rewrittenOperand); return _factory.Call(null, method, rewrittenOperand); } + + throw ExceptionUtilities.Unreachable(); } default: diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs index 2e808a3d5ec6f..67b214853ab3a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenReadOnlySpanConstructionTest.cs @@ -135,10 +135,10 @@ .maxstack 2 .locals init (System.ReadOnlySpan V_0, //s1 System.ReadOnlySpan V_1) //s2 IL_0000: ldstr """" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldstr """" - IL_0010: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0010: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_0015: stloc.1 IL_0016: ldloca.s V_0 IL_0018: call ""int System.ReadOnlySpan.Length.get"" @@ -147,10 +147,10 @@ .locals init (System.ReadOnlySpan V_0, //s1 IL_0024: ceq IL_0026: call ""void System.Console.Write(bool)"" IL_002b: ldnull - IL_002c: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_002c: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_0031: stloc.0 IL_0032: ldnull - IL_0033: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0033: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_0038: stloc.1 IL_0039: ldloca.s V_0 IL_003b: call ""int System.ReadOnlySpan.Length.get"" @@ -612,7 +612,7 @@ public static void Test1(ReadOnlySpan arg1, ReadOnlySpan arg2) // Code size 28 (0x1c) .maxstack 3 IL_0000: ldstr ""QWERTYUIOP"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: ldsflda "".__StaticArrayInitTypeSize=10 .C848E1013F9F04A9D63FA43CE7FD4AF035152C7C669A4A404B67107CEE5F2E4E"" IL_000f: ldc.i4.s 10 IL_0011: newobj ""System.ReadOnlySpan..ctor(void*, int)"" diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/ForeachTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/ForeachTest.cs index 92fb52b297fc0..4eee673fa0ff1 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/ForeachTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/ForeachTest.cs @@ -504,7 +504,7 @@ .maxstack 2 .locals init (System.ReadOnlySpan V_0, int V_1) IL_0000: ldstr ""hello"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.0 IL_000c: stloc.1 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs index 0749e47f7df78..8d3003d88174b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs @@ -826,7 +826,7 @@ public static void Main() } } }"; - var comp = CreateCompilationWithIndexAndRangeAndSpan(src, TestOptions.ReleaseExe); + var comp = CreateCompilationWithIndexAndRangeAndSpanAndMemoryExtensions(src, TestOptions.ReleaseExe); var verifier = CompileAndVerify(comp, expectedOutput: @" abcd abcd"); @@ -848,7 +848,7 @@ .locals init (System.ReadOnlySpan V_0, //span IL_000a: callvirt ""int string.Length.get"" IL_000f: callvirt ""string string.Substring(int, int)"" IL_0014: call ""void System.Console.WriteLine(string)"" - IL_0019: call ""System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(string)"" + IL_0019: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_001e: stloc.0 IL_001f: ldloca.s V_0 IL_0021: stloc.s V_4 @@ -1081,7 +1081,7 @@ .locals init (System.Span V_0) //s [Fact] public void PatternIndexAndRangeSpanChar() { - var comp = CreateCompilationWithIndexAndRangeAndSpan(@" + var comp = CreateCompilationWithIndexAndRangeAndSpanAndMemoryExtensions(@" using System; class C { @@ -1110,7 +1110,7 @@ .locals init (System.ReadOnlySpan V_0, //s int V_3, int V_4) IL_0000: ldstr ""abcdefg"" - IL_0005: call ""System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_0 IL_000d: dup diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs index 745c0e4d1c167..6df4ef7aef6c9 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/InlineArrayTests.cs @@ -17019,12 +17019,22 @@ static void Main() } } "; - var comp = CreateCompilation(src + Buffer10Definition, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src + Buffer10Definition, parseOptions: TestOptions.Regular13, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); comp.VerifyDiagnostics( // (16,31): error CS0457: Ambiguous user defined conversions 'C.implicit operator C(ReadOnlySpan)' and 'C.implicit operator C(Span)' when converting from 'Buffer10' to 'C' // System.Console.Write(((C)b).F); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(C)b").WithArguments("C.implicit operator C(System.ReadOnlySpan)", "C.implicit operator C(System.Span)", "Buffer10", "C").WithLocation(16, 31) ); + + // NOTE: No longer ambiguous because there is a standard implicit span conversion from Span to ReadOnlySpan which makes the Span operator better. + + var expectedOutput = ExecutionConditionUtil.IsCoreClr ? "110" : null; + + comp = CreateCompilation(src + Buffer10Definition, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.Fails).VerifyDiagnostics(); + + comp = CreateCompilation(src + Buffer10Definition, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.Fails).VerifyDiagnostics(); } [Fact] @@ -17054,12 +17064,27 @@ static void Test(in Buffer10 b) } } "; - var comp = CreateCompilation(src + Buffer10Definition, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(src + Buffer10Definition, parseOptions: TestOptions.Regular13, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); comp.VerifyDiagnostics( // (21,31): error CS0457: Ambiguous user defined conversions 'C.implicit operator C(ReadOnlySpan)' and 'C.implicit operator C(Span)' when converting from 'Buffer10' to 'C' // System.Console.Write(((C)b).F); Diagnostic(ErrorCode.ERR_AmbigUDConv, "(C)b").WithArguments("C.implicit operator C(System.ReadOnlySpan)", "C.implicit operator C(System.Span)", "Buffer10", "C").WithLocation(21, 31) ); + + // NOTE: No longer ambiguous because there is a standard implicit span conversion from Span to ReadOnlySpan which makes the Span operator better. + + var expectedDiagnostics = new[] + { + // (21,34): error CS9164: Cannot convert expression to 'Span' because it is not an assignable variable + // System.Console.Write(((C)b).F); + Diagnostic(ErrorCode.ERR_InlineArrayConversionToSpanNotSupported, "b").WithArguments("System.Span").WithLocation(21, 34) + }; + + comp = CreateCompilation(src + Buffer10Definition, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(src + Buffer10Definition, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(expectedDiagnostics); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests.cs index c31180383fb4a..514fafa67a90b 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests.cs @@ -7512,7 +7512,7 @@ .maxstack 2 .locals init (System.ReadOnlySpan V_0, //chars int V_1) IL_0000: ldstr ""string 2"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_0 IL_000d: call ""int System.ReadOnlySpan.Length.get"" diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs index 6cdc9572dc7d3..c9e40b28b64ba 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs @@ -7117,10 +7117,7 @@ struct Example() Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), - // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); } public static IEnumerable ParameterScope_MemberData() diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 19dd483b3b044..be10c6f61e3b8 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -323,13 +323,255 @@ .maxstack 1 """); } + [Theory, CombinatorialData] + public void Conversion_Span_ReadOnlySpan_Implicit( + [CombinatorialLangVersions] LanguageVersion langVersion, + bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static Span source() => new Span(new int[] { 1, 2, 3 }); + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x); } } + """; + + var expectedOutput = "123"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """); + } + + [Theory, CombinatorialData] + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit(bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static Span source() => new Span(new[] { "a", "b" }); + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x); } } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + if (!cast) + { + comp.VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 26)); + } + else + { + comp.VerifyDiagnostics( + // (2,26): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = (ReadOnlySpan)source(); + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 26)); + } + + var expectedOutput = "ab"; + + var expectedIL = """ + { + // Code size 21 (0x15) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000f: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_0014: ret + } + """; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + } + + [Theory, CombinatorialData] + public void Conversion_Span_ReadOnlySpan_Implicit_IdentityInnerConversion( + [CombinatorialLangVersions] LanguageVersion langVersion, + bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static Span source() => new Span(new object[] { "a", "b" }); + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x); } } + """; + + var expectedOutput = "ab"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """); + } + + [Theory, CombinatorialData] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit(bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static ReadOnlySpan source() => new ReadOnlySpan(new[] { "a", "b" }); + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x); } } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + if (!cast) + { + comp.VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(2, 26)); + } + else + { + comp.VerifyDiagnostics( + // (2,26): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan s = (ReadOnlySpan)source(); + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)source()").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(2, 26)); + } + + var expectedOutput = "ab"; + + var expectedIL = """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000a: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + } + + [Theory, CombinatorialData] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_IdentityInnerConversion( + [CombinatorialLangVersions] LanguageVersion langVersion, + bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static ReadOnlySpan source() => new ReadOnlySpan(new string[] { "a", "b" }); + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x); } } + """; + + var expectedOutput = "ab"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 11 (0xb) + .maxstack 1 + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000a: ret + } + """); + } + + [Theory, CombinatorialData] + public void Conversion_String_ReadOnlySpan_Implicit(bool cast) + { + var source = $$""" + using System; + ReadOnlySpan s = {{(cast ? "(ReadOnlySpan)" : "")}}source(); + report(s); + static string source() => "abc"; + static void report(ReadOnlySpan s) { foreach (var x in s) { Console.Write(x + " "); } } + """; + + var expectedOutput = "a b c"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + var owner = ExecutionConditionUtil.IsCoreClr ? "string" : "System.ReadOnlySpan"; + verifier.VerifyIL("", $$""" + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan {{owner}}.op_Implicit(string)" + IL_000a: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """); + + var expectedIL = """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000a: call "void Program.<
$>g__report|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", expectedIL); + } + [Fact] public void Conversion_Array_Span_Implicit_MissingHelper() { var source = """ using System; Span s = arr(); - static int[] arr() => new int[] { 1, 2, 3 }; + static int[] arr() => throw null; namespace System { @@ -338,311 +580,1234 @@ public readonly ref struct Span } } """; - CreateCompilation(source).VerifyDiagnostics( + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,15): error CS0029: Cannot implicitly convert type 'int[]' to 'System.Span' + // Span s = arr(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arr()").WithArguments("int[]", "System.Span").WithLocation(2, 15)); + + var expectedDiagnostics = new[] + { // (2,15): error CS0656: Missing compiler required member 'System.Span.op_Implicit' // Span s = arr(); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } - [Theory, CombinatorialData] - public void Conversion_Array_Span_Implicit_DifferentOperator( - [CombinatorialValues("int[]", "T[][]")] string parameterType) + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_MissingHelper() { - var source = $$""" + var source = """ using System; - Span s = arr(); - static int[] arr() => new int[] { 1, 2, 3 }; + ReadOnlySpan s = source(); + static Span source() => throw null; namespace System { - public readonly ref struct Span - { - public static implicit operator Span({{parameterType}} array) => throw null; - } + public readonly ref struct Span { } + public readonly ref struct ReadOnlySpan { } } """; - CreateCompilation(source).VerifyDiagnostics( - // (2,15): error CS0656: Missing compiler required member 'System.Span.op_Implicit' - // Span s = arr(); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,23): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 23)); + + var expectedDiagnostics = new[] + { + // (2,23): error CS0656: Missing compiler required member 'Span.op_Implicit' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 23) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Fact] - public void Conversion_Array_Span_Implicit_ExplicitOperator() + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_MissingHelper_01() { var source = """ using System; - Span s = arr(); - static int[] arr() => new int[] { 1, 2, 3 }; + ReadOnlySpan s = source(); + static Span source() => throw null; namespace System { - public readonly ref struct Span - { - public static explicit operator Span(T[] array) => throw null; - } + public readonly ref struct Span { } + public readonly ref struct ReadOnlySpan { } } """; - CreateCompilation(source).VerifyDiagnostics( - // (2,15): error CS0656: Missing compiler required member 'System.Span.op_Implicit' - // Span s = arr(); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 26)); + + var expectedDiagnostics = new[] + { + // (2,26): error CS0656: Missing compiler required member 'Span.op_Implicit' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 26), + // (2,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 26) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Fact] - public void Conversion_Array_Span_Explicit_ExplicitOperator() + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_MissingHelper_02() { var source = """ using System; - Span s = (Span)arr(); - static int[] arr() => new int[] { 1, 2, 3 }; + ReadOnlySpan s = source(); + static Span source() => throw null; namespace System { public readonly ref struct Span { - public static explicit operator Span(T[] array) => throw null; + public static implicit operator ReadOnlySpan(Span s) => throw null; } + public readonly ref struct ReadOnlySpan { } } """; - CreateCompilation(source).VerifyDiagnostics( - // (2,15): error CS0656: Missing compiler required member 'Span.op_Implicit' - // Span s = (Span)arr(); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "(Span)arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 26)); + + var expectedDiagnostics = new[] + { + // (2,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 26) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Fact] - public void Conversion_Array_Span_Implicit_UnrecognizedModreq() + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_MissingHelper_03() { - /* - public struct Span - { - public static implicit operator modreq(A) Span(T[] array) => throw null; - } - public class A { } - public static class C - { - public static void M(Span s) { } - } - */ - var ilSource = """ - .class public sequential ansi sealed beforefieldinit System.Span`1 extends System.ValueType + var source = """ + using System; + ReadOnlySpan s = source(); + static Span source() => throw null; + + namespace System { - .pack 0 - .size 1 - .method public hidebysig specialname static valuetype System.Span`1 modreq(A) op_Implicit(!T[] 'array') cil managed + public readonly ref struct Span { } + public readonly ref struct ReadOnlySpan { - .maxstack 1 - ret + public static ReadOnlySpan CastUp(ReadOnlySpan s) where TDerived : class, T => throw null; } } - .class public auto ansi sealed beforefieldinit A extends System.Object - { - } - .class public auto ansi abstract sealed beforefieldinit C extends System.Object - { - .method public hidebysig static void M(valuetype System.Span`1 s) cil managed - { - .maxstack 1 - ret - } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(2, 26)); + + var expectedDiagnostics = new[] + { + // (2,26): error CS0656: Missing compiler required member 'Span.op_Implicit' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 26) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MissingHelper() + { + var source = """ + using System; + ReadOnlySpan s = source(); + static ReadOnlySpan source() => throw null; + + namespace System + { + public readonly ref struct ReadOnlySpan { } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,26): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(2, 26)); + + var expectedDiagnostics = new[] + { + // (2,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 26) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_MissingHelper() + { + var source = """ + using System; + ReadOnlySpan s = source(); + static string source() => throw null; + + namespace System + { + public readonly ref struct ReadOnlySpan { } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (2,24): error CS0029: Cannot implicitly convert type 'string' to 'System.ReadOnlySpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "source()").WithArguments("string", "System.ReadOnlySpan").WithLocation(2, 24)); + + var expectedDiagnostics = new[] + { + // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 24) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, CombinatorialData] + public void Conversion_Array_Span_Implicit_DifferentOperator( + [CombinatorialValues("int[]", "T[][]")] string parameterType) + { + var source = $$""" + using System; + Span s = arr(); + static int[] arr() => new int[] { 1, 2, 3 }; + + namespace System + { + public readonly ref struct Span + { + public static implicit operator Span({{parameterType}} array) => throw null; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (2,15): error CS0656: Missing compiler required member 'System.Span.op_Implicit' + // Span s = arr(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + } + + [Theory] + [InlineData("ReadOnlySpan AsSpan(string s)", true)] + [InlineData("ReadOnlySpan AsSpan(string s)", false)] + [InlineData("ReadOnlySpan AsSpan(string s)", false)] + [InlineData("ReadOnlySpan AsSpan(object o)", false)] + public void Conversion_Array_Span_Implicit_DifferentHelper(string signature, bool works) + { + var source = $$""" + using System; + ReadOnlySpan s = source(); + static string source() => ""; + + namespace System + { + public readonly ref struct ReadOnlySpan { } + public static class MemoryExtensions + { + public static {{signature}} => default; + } + } + """; + var comp = CreateCompilation(source); + if (works) + { + // Make sure the code above can work so we are testing something useful. + comp.VerifyDiagnostics(); + } + else + { + comp.VerifyDiagnostics( + // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 24)); + } + } + + [Fact] + public void Conversion_Array_Span_Implicit_ExplicitOperator() + { + var source = """ + using System; + Span s = arr(); + static int[] arr() => new int[] { 1, 2, 3 }; + + namespace System + { + public readonly ref struct Span + { + public static explicit operator Span(T[] array) => throw null; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (2,15): error CS0656: Missing compiler required member 'System.Span.op_Implicit' + // Span s = arr(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + } + + [Fact] + public void Conversion_Array_Span_Explicit_ExplicitOperator() + { + var source = """ + using System; + Span s = (Span)arr(); + static int[] arr() => new int[] { 1, 2, 3 }; + + namespace System + { + public readonly ref struct Span + { + public static explicit operator Span(T[] array) => throw null; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (2,15): error CS0656: Missing compiler required member 'Span.op_Implicit' + // Span s = (Span)arr(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "(Span)arr()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 15)); + } + + [Fact] + public void Conversion_Array_Span_Implicit_UnrecognizedModreq() + { + /* + public struct Span + { + public static implicit operator modreq(A) Span(T[] array) => throw null; + } + public class A { } + public static class C + { + public static void M(Span s) { } + } + */ + var ilSource = """ + .class public sequential ansi sealed beforefieldinit System.Span`1 extends System.ValueType + { + .pack 0 + .size 1 + .method public hidebysig specialname static valuetype System.Span`1 modreq(A) op_Implicit(!T[] 'array') cil managed + { + .maxstack 1 + ret + } + } + .class public auto ansi sealed beforefieldinit A extends System.Object + { + } + .class public auto ansi abstract sealed beforefieldinit C extends System.Object + { + .method public hidebysig static void M(valuetype System.Span`1 s) cil managed + { + .maxstack 1 + ret + } + } + """; + var source = """ + C.M(new int[] { 1, 2, 3 }); + """; + CreateCompilationWithIL(source, ilSource).VerifyDiagnostics( + // (1,5): error CS0570: 'Span.implicit operator Span(T[])' is not supported by the language + // C.M(new int[] { 1, 2, 3 }); + Diagnostic(ErrorCode.ERR_BindToBogus, "new int[] { 1, 2, 3 }").WithArguments("System.Span.implicit operator System.Span(T[])").WithLocation(1, 5)); + } + + [Fact] + public void Conversion_Array_Span_Implicit_UnrecognizedModopt() + { + /* + public struct Span + { + public static implicit operator modopt(A) Span(T[] array) => throw null; + } + public class A { } + public static class C + { + public static void M(Span s) { } + } + */ + var ilSource = """ + .class public sequential ansi sealed beforefieldinit System.Span`1 extends System.ValueType + { + .pack 0 + .size 1 + .method public hidebysig specialname static valuetype System.Span`1 modopt(A) op_Implicit(!T[] 'array') cil managed + { + .maxstack 1 + ret + } + } + .class public auto ansi sealed beforefieldinit A extends System.Object + { + } + .class public auto ansi abstract sealed beforefieldinit C extends System.Object + { + .method public hidebysig static void M(valuetype System.Span`1 s) cil managed + { + .maxstack 1 + ret + } + } + """; + var source = """ + C.M(arr()); + static int[] arr() => new int[] { 1, 2, 3 }; + """; + var comp = CreateCompilationWithIL(source, ilSource); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "int[] Program.<
$>g__arr|0_0()" + IL_0005: call "System.Span System.Span.op_Implicit(int[])" + IL_000a: call "void C.M(System.Span)" + IL_000f: ret + } + """); + } + + [Theory] + [InlineData("where TDerived : T")] + [InlineData("")] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MissingClassConstraint(string constraints) + { + var source = $$""" + using System; + ReadOnlySpan s = source(); + static ReadOnlySpan source() => throw null; + + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static ReadOnlySpan CastUp(ReadOnlySpan items) {{constraints}} => throw null; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (2,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 26)); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MissingInheritsConstraint() + { + var source = """ + using System; + ReadOnlySpan s = source(); + static ReadOnlySpan source() => throw null; + + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static ReadOnlySpan CastUp(ReadOnlySpan items) where TDerived : class => throw null; + } + } + """; + var verifier = CompileAndVerify(source); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000a: pop + IL_000b: ret + } + """); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_AdditionalConstraint() + { + var source = """ + using System; + ReadOnlySpan s = source(); + static ReadOnlySpan source() => throw null; + + public class C { } + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static ReadOnlySpan CastUp(ReadOnlySpan items) where TDerived : C => throw null; + } + } + """; + CreateCompilation(source).VerifyDiagnostics( + // (2,26): error CS0311: The type 'string' cannot be used as type parameter 'TDerived' in the generic type or method 'ReadOnlySpan.CastUp(ReadOnlySpan)'. There is no implicit reference conversion from 'string' to 'C'. + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "source()").WithArguments("System.ReadOnlySpan.CastUp(System.ReadOnlySpan)", "C", "TDerived", "string").WithLocation(2, 26)); + } + + [Theory] + [InlineData("T", "T", false)] + [InlineData("T", "TDerived", false)] + [InlineData("TDerived", "TDerived", false)] + [InlineData("TDerived", "T", true)] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_WrongTypeArgument(string arg, string result, bool works) + { + var source = $$""" + using System; + ReadOnlySpan s = source(); + static ReadOnlySpan source() => throw null; + + public class C { } + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static ReadOnlySpan<{{result}}> CastUp(ReadOnlySpan<{{arg}}> items) where TDerived : class, T => throw null; + } + } + """; + var comp = CreateCompilation(source); + if (works) + { + // Make sure the code above can work so we are testing something useful. + comp.VerifyDiagnostics(); + } + else + { + comp.VerifyDiagnostics( + // (2,26): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 26)); + } + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Implicit_ConstantData(LanguageVersion langVersion) + { + var source = """ + using System; + + C.M1(new[] { 1 }); + C.M2(new[] { 2 }); + + static class C + { + public static void M1(Span s) => Console.Write(s[0]); + public static void M2(ReadOnlySpan s) => Console.Write(s[0]); + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); + if (ExecutionConditionUtil.IsCoreClr) + { + verifier.VerifyIL("", """ + { + // Code size 36 (0x24) + .maxstack 4 + IL_0000: ldc.i4.1 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: stelem.i4 + IL_000a: call "System.Span System.Span.op_Implicit(int[])" + IL_000f: call "void C.M1(System.Span)" + IL_0014: ldtoken ".__StaticArrayInitTypeSize=4_Align=4 .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE4" + IL_0019: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" + IL_001e: call "void C.M2(System.ReadOnlySpan)" + IL_0023: ret + } + """); + } + else + { + verifier.VerifyIL("", """ + { + // Code size 63 (0x3f) + .maxstack 4 + IL_0000: ldc.i4.1 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: stelem.i4 + IL_000a: call "System.Span System.Span.op_Implicit(int[])" + IL_000f: call "void C.M1(System.Span)" + IL_0014: ldsfld "int[] .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE_A6" + IL_0019: dup + IL_001a: brtrue.s IL_0034 + IL_001c: pop + IL_001d: ldc.i4.1 + IL_001e: newarr "int" + IL_0023: dup + IL_0024: ldtoken "int .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE" + IL_0029: call "void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)" + IL_002e: dup + IL_002f: stsfld "int[] .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE_A6" + IL_0034: newobj "System.ReadOnlySpan..ctor(int[])" + IL_0039: call "void C.M2(System.ReadOnlySpan)" + IL_003e: ret + } + """); + } + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Implicit_ConstantData_NotWellKnownSpan(LanguageVersion langVersion) + { + var source = """ + extern alias span; + C.M1(new[] { 1 }); + C.M2(new[] { 2 }); + static class C + { + public static void M1(span::System.Span s) => System.Console.Write(s[0]); + public static void M2(span::System.ReadOnlySpan s) => System.Console.Write(s[0]); + } + """; + var spanDll = CreateCompilation(TestSources.Span, options: TestOptions.UnsafeReleaseDll) + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span"]); + var verifier = CompileAndVerify([source, TestSources.Span], + references: [spanDll], + expectedOutput: "12", + verify: Verification.Fails, + // warning CS0436: Type conflicts with imported type + options: TestOptions.UnsafeReleaseExe.WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress), + parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 41 (0x29) + .maxstack 4 + IL_0000: ldc.i4.1 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: stelem.i4 + IL_000a: call "System.Span System.Span.op_Implicit(int[])" + IL_000f: call "void C.M1(System.Span)" + IL_0014: ldc.i4.1 + IL_0015: newarr "int" + IL_001a: dup + IL_001b: ldc.i4.0 + IL_001c: ldc.i4.2 + IL_001d: stelem.i4 + IL_001e: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(int[])" + IL_0023: call "void C.M2(System.ReadOnlySpan)" + IL_0028: ret + } + """); + } + + [Theory, CombinatorialData] + public void Conversion_Array_Span_Implicit_MultipleSpans_01( + [CombinatorialValues("Span", "ReadOnlySpan")] string type) + { + string getSpanSource(string output) => $$""" + namespace System + { + public readonly ref struct {{type}} + { + public static implicit operator {{type}}(T[] array) + { + Console.Write("{{output}}"); + return default; + } + } + } + """; + + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(); + + var source = $$""" + using System; + {{type}} s = arr(); + use(s); + static int[] arr() => new int[] { 1, 2, 3 }; + static void use({{type}} s) { } + """; + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); + verifier.VerifyDiagnostics( + // (2,1): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. + // Span s = arr(); + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(2, 1), + // (5,17): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. + // static void use(Span s) { } + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(5, 17), + // (5,41): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. + // public static implicit operator Span(T[] array) + Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(5, 41)); + + verifier.VerifyIL("", $$""" + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "int[] Program.<
$>g__arr|0_0()" + IL_0005: call "System.{{type}} System.{{type}}.op_Implicit(int[])" + IL_000a: call "void Program.<
$>g__use|0_1(System.{{type}})" + IL_000f: ret + } + """); + } + + [Theory, CombinatorialData] + public void Conversion_Array_Span_Implicit_MultipleSpans_02( + [CombinatorialValues("Span", "ReadOnlySpan")] string type) + { + string getSpanSource(string output) => $$""" + namespace System + { + public readonly ref struct {{type}} + { + public static implicit operator {{type}}(T[] array) + { + Console.Write("{{output}}"); + return default; + } + } + } + """; + + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["lib"]); + + var source = $$""" + extern alias lib; + lib::System.{{type}} s = arr(); + static int[] arr() => new int[] { 1, 2, 3 }; + """; + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "External"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", $$""" + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "int[] Program.<
$>g__arr|0_0()" + IL_0005: call "System.{{type}} System.{{type}}.op_Implicit(int[])" + IL_000a: pop + IL_000b: ret + } + """); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_MultipleSpans_01() + { + static string getSpanSource(string output) => $$""" + namespace System + { + public readonly ref struct ReadOnlySpan; + public readonly ref struct Span + { + public static implicit operator ReadOnlySpan(Span span) + { + Console.Write("{{output}}"); + return default; + } + } + } + """; + + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(); + + var source = """ + using System; + ReadOnlySpan s = source(); + use(s); + static Span source() => default; + static void use(ReadOnlySpan s) { } + """; + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer", + // warning CS0436: Type conflicts with imported type + options: TestOptions.UnsafeReleaseExe.WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress)); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", """ + { + // Code size 16 (0x10) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: call "void Program.<
$>g__use|0_1(System.ReadOnlySpan)" + IL_000f: ret + } + """); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_MultipleSpans_02() + { + static string getSpanSource(string output) => $$""" + namespace System + { + public readonly ref struct ReadOnlySpan; + public readonly ref struct Span + { + public static implicit operator ReadOnlySpan(Span span) + { + Console.Write("{{output}}"); + return default; + } + } + } + """; + + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["lib"]); + + var source = """ + extern alias lib; + lib::System.ReadOnlySpan s = source(); + static lib::System.Span source() => default; + """; + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "External"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: pop + IL_000b: ret + } + """); + + source = """ + extern alias lib; + lib::System.ReadOnlySpan s = source(); + static System.Span source() => default; + """; + CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer").VerifyDiagnostics( + // (2,35): error CS0656: Missing compiler required member 'Span.op_Implicit' + // lib::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 35)); + + source = """ + extern alias lib; + System.ReadOnlySpan s = source(); + static lib::System.Span source() => default; + """; + CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer").VerifyDiagnostics( + // (2,30): error CS0656: Missing compiler required member 'Span.op_Implicit' + // System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 30)); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_MultipleSpans_03() + { + var span1 = CreateCompilation(""" + namespace System; + public readonly ref struct ReadOnlySpan; + public readonly ref struct Span; + """, assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span1"]); + var span2 = CreateCompilation(""" + extern alias span1; + namespace System; + public readonly ref struct ReadOnlySpan; + public readonly ref struct Span + { + public static implicit operator span1::System.ReadOnlySpan(Span span) + { + Console.Write("Span2"); + return default; + } + } + """, [span1], assemblyName: "Span2") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span2"]); + + var source = """ + extern alias span1; + extern alias span2; + span1::System.ReadOnlySpan s = source(); + static span2::System.Span source() => default; + """; + + var comp = CreateCompilation(source, [span1, span2], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Span2"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: pop + IL_000b: ret } - """; - var source = """ - C.M(new int[] { 1, 2, 3 }); - """; - CreateCompilationWithIL(source, ilSource).VerifyDiagnostics( - // (1,5): error CS0570: 'Span.implicit operator Span(T[])' is not supported by the language - // C.M(new int[] { 1, 2, 3 }); - Diagnostic(ErrorCode.ERR_BindToBogus, "new int[] { 1, 2, 3 }").WithArguments("System.Span.implicit operator System.Span(T[])").WithLocation(1, 5)); + """); + + source = """ + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span2::System.Span source() => default; + """; + CreateCompilation(source, [span2], assemblyName: "Consumer").VerifyDiagnostics( + // (2,37): error CS0656: Missing compiler required member 'Span.op_Implicit' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(2, 37)); + + source = """ + extern alias span1; + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span1::System.Span source() => default; + """; + CreateCompilation(source, [span1, span2], assemblyName: "Consumer").VerifyDiagnostics( + // (3,37): error CS0656: Missing compiler required member 'Span.op_Implicit' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.Span", "op_Implicit").WithLocation(3, 37)); } [Fact] - public void Conversion_Array_Span_Implicit_UnrecognizedModopt() + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_MultipleSpans() { - /* - public struct Span - { - public static implicit operator modopt(A) Span(T[] array) => throw null; - } - public class A { } - public static class C + var span1 = CreateCompilation(""" + namespace System; + public readonly ref struct Span; + public readonly ref struct ReadOnlySpan { - public static void M(Span s) { } + public static ReadOnlySpan CastUp(ReadOnlySpan span) where TDerived : class, T + { + Console.WriteLine("CastUp"); + return default; + } } - */ - var ilSource = """ - .class public sequential ansi sealed beforefieldinit System.Span`1 extends System.ValueType + """, assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span1"]); + var span2 = CreateCompilation(""" + extern alias span1; + namespace System; + public readonly ref struct Span { - .pack 0 - .size 1 - .method public hidebysig specialname static valuetype System.Span`1 modopt(A) op_Implicit(!T[] 'array') cil managed + public static implicit operator span1::System.ReadOnlySpan(Span span) { - .maxstack 1 - ret + Console.WriteLine("Span2 to ReadOnlySpan1"); + return default; } } - .class public auto ansi sealed beforefieldinit A extends System.Object + """, [span1], assemblyName: "Span2") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span2"]); + + var source = """ + extern alias span1; + extern alias span2; + span1::System.ReadOnlySpan s = source(); + static span2::System.Span source() => default; + """; + + var comp = CreateCompilation(source, [span1, span2], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: """ + Span2 to ReadOnlySpan1 + CastUp + """); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ { + // Code size 17 (0x11) + .maxstack 1 + IL_0000: call "System.Span Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_000a: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000f: pop + IL_0010: ret } - .class public auto ansi abstract sealed beforefieldinit C extends System.Object + """); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MultipleSpans_01() + { + static string getSpanSource(string output) => $$""" + namespace System { - .method public hidebysig static void M(valuetype System.Span`1 s) cil managed + public readonly ref struct ReadOnlySpan { - .maxstack 1 - ret + public static ReadOnlySpan CastUp(ReadOnlySpan other) where TDerived : class, T + { + Console.Write("{{output}}"); + return default; + } } } """; + + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(); + var source = """ - C.M(arr()); - static int[] arr() => new int[] { 1, 2, 3 }; + using System; + ReadOnlySpan s = source(); + use(s); + static ReadOnlySpan source() => default; + static void use(ReadOnlySpan s) { } """; - var comp = CreateCompilationWithIL(source, ilSource); - var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer", + // warning CS0436: Type conflicts with imported type + options: TestOptions.UnsafeReleaseExe.WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress)); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ { // Code size 16 (0x10) .maxstack 1 - IL_0000: call "int[] Program.<
$>g__arr|0_0()" - IL_0005: call "System.Span System.Span.op_Implicit(int[])" - IL_000a: call "void C.M(System.Span)" + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000a: call "void Program.<
$>g__use|0_1(System.ReadOnlySpan)" IL_000f: ret } """); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Implicit_ConstantData(LanguageVersion langVersion) + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MultipleSpans_02() { - var source = """ - using System; + static string getSpanSource(string output) => $$""" + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static ReadOnlySpan CastUp(ReadOnlySpan other) where TDerived : class, T + { + Console.Write("{{output}}"); + return default; + } + } + } + """; - C.M1(new[] { 1 }); - C.M2(new[] { 2 }); + var spanComp = CreateCompilation(getSpanSource("External"), assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["lib"]); - static class C + var source = """ + extern alias lib; + lib::System.ReadOnlySpan s = source(); + static lib::System.ReadOnlySpan source() => default; + """; + + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "External"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ { - public static void M1(Span s) => Console.Write(s[0]); - public static void M2(ReadOnlySpan s) => Console.Write(s[0]); + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000a: pop + IL_000b: ret } + """); + + source = """ + extern alias lib; + lib::System.ReadOnlySpan s = source(); + static System.ReadOnlySpan source() => default; """; + CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer").VerifyDiagnostics( + // (2,38): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // lib::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 38)); - var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); - var verifier = CompileAndVerify(comp, expectedOutput: "12").VerifyDiagnostics(); - if (ExecutionConditionUtil.IsCoreClr) - { - verifier.VerifyIL("", """ - { - // Code size 36 (0x24) - .maxstack 4 - IL_0000: ldc.i4.1 - IL_0001: newarr "int" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldc.i4.1 - IL_0009: stelem.i4 - IL_000a: call "System.Span System.Span.op_Implicit(int[])" - IL_000f: call "void C.M1(System.Span)" - IL_0014: ldtoken ".__StaticArrayInitTypeSize=4_Align=4 .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE4" - IL_0019: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" - IL_001e: call "void C.M2(System.ReadOnlySpan)" - IL_0023: ret - } - """); - } - else - { - verifier.VerifyIL("", """ + source = """ + extern alias lib; + System.ReadOnlySpan s = source(); + static lib::System.ReadOnlySpan source() => default; + """; + CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer").VerifyDiagnostics( + // (2,33): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 33)); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MultipleSpans_03() + { + var span1 = CreateCompilation(""" + namespace System; + public readonly ref struct ReadOnlySpan; + """, assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span1"]); + var span2 = CreateCompilation(""" + extern alias span1; + namespace System; + public readonly ref struct ReadOnlySpan + { + public static span1::System.ReadOnlySpan CastUp(ReadOnlySpan other) where TDerived : class, T { - // Code size 63 (0x3f) - .maxstack 4 - IL_0000: ldc.i4.1 - IL_0001: newarr "int" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldc.i4.1 - IL_0009: stelem.i4 - IL_000a: call "System.Span System.Span.op_Implicit(int[])" - IL_000f: call "void C.M1(System.Span)" - IL_0014: ldsfld "int[] .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE_A6" - IL_0019: dup - IL_001a: brtrue.s IL_0034 - IL_001c: pop - IL_001d: ldc.i4.1 - IL_001e: newarr "int" - IL_0023: dup - IL_0024: ldtoken "int .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE" - IL_0029: call "void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)" - IL_002e: dup - IL_002f: stsfld "int[] .26B25D457597A7B0463F9620F666DD10AA2C4373A505967C7C8D70922A2D6ECE_A6" - IL_0034: newobj "System.ReadOnlySpan..ctor(int[])" - IL_0039: call "void C.M2(System.ReadOnlySpan)" - IL_003e: ret + Console.Write("Span2"); + return default; } - """); - } + } + """, [span1], assemblyName: "Span2") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span2"]); + + var source = """ + extern alias span1; + extern alias span2; + span1::System.ReadOnlySpan s = source(); + static span2::System.ReadOnlySpan source() => default; + """; + CreateCompilation(source, [span1, span2], assemblyName: "Consumer").VerifyDiagnostics( + // (3,40): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // span1::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(3, 40)); + + source = """ + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span2::System.ReadOnlySpan source() => default; + """; + CreateCompilation(source, [span2], assemblyName: "Consumer").VerifyDiagnostics( + // (2,40): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 40)); + + source = """ + extern alias span1; + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span1::System.ReadOnlySpan source() => default; + """; + CreateCompilation(source, [span1, span2], assemblyName: "Consumer").VerifyDiagnostics( + // (3,40): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(3, 40)); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Implicit_ConstantData_NotWellKnownSpan(LanguageVersion langVersion) + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_MultipleSpans_04() { - var source = """ - extern alias span; - C.M1(new[] { 1 }); - C.M2(new[] { 2 }); - static class C + var span1 = CreateCompilation(""" + namespace System; + public readonly ref struct ReadOnlySpan; + """, assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span1"]); + var span2 = CreateCompilation(""" + extern alias span1; + namespace System; + public readonly ref struct ReadOnlySpan { - public static void M1(span::System.Span s) => System.Console.Write(s[0]); - public static void M2(span::System.ReadOnlySpan s) => System.Console.Write(s[0]); + public static ReadOnlySpan CastUp(span1::System.ReadOnlySpan other) where TDerived : class, T + { + Console.Write("Span2"); + return default; + } } - """; - var spanDll = CreateCompilation(TestSources.Span, options: TestOptions.UnsafeReleaseDll) + """, [span1], assemblyName: "Span2") .VerifyDiagnostics() - .EmitToImageReference(aliases: ["span"]); - var verifier = CompileAndVerify([source, TestSources.Span], - references: [spanDll], - expectedOutput: "12", - verify: Verification.Fails, - // warning CS0436: Type conflicts with imported type - options: TestOptions.UnsafeReleaseExe.WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress), - parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + .EmitToImageReference(aliases: ["span2"]); + + var source = """ + extern alias span1; + extern alias span2; + span1::System.ReadOnlySpan s = source(); + static span2::System.ReadOnlySpan source() => default; + """; + CreateCompilation(source, [span1, span2], assemblyName: "Consumer").VerifyDiagnostics( + // (3,40): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // span1::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(3, 40)); + + source = """ + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span2::System.ReadOnlySpan source() => default; + """; + CreateCompilation(source, [span2], assemblyName: "Consumer").VerifyDiagnostics( + // (2,40): error CS0656: Missing compiler required member 'ReadOnlySpan.CastUp' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.ReadOnlySpan", "CastUp").WithLocation(2, 40)); + + source = """ + extern alias span1; + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static span1::System.ReadOnlySpan source() => default; + """; + var comp = CreateCompilation(source, [span1, span2], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Span2"); verifier.VerifyDiagnostics(); verifier.VerifyIL("", """ { - // Code size 41 (0x29) - .maxstack 4 - IL_0000: ldc.i4.1 - IL_0001: newarr "int" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldc.i4.1 - IL_0009: stelem.i4 - IL_000a: call "System.Span System.Span.op_Implicit(int[])" - IL_000f: call "void C.M1(System.Span)" - IL_0014: ldc.i4.1 - IL_0015: newarr "int" - IL_001a: dup - IL_001b: ldc.i4.0 - IL_001c: ldc.i4.2 - IL_001d: stelem.i4 - IL_001e: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(int[])" - IL_0023: call "void C.M2(System.ReadOnlySpan)" - IL_0028: ret + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "System.ReadOnlySpan Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000a: pop + IL_000b: ret } """); } - [Theory, CombinatorialData] - public void Conversion_Array_Span_Implicit_MultipleSpans_01( - [CombinatorialValues("Span", "ReadOnlySpan")] string type) + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_MultipleSpans_01() { - string getSpanSource(string output) => $$""" + static string getSpanSource(string output) => $$""" namespace System { - public readonly ref struct {{type}} + public readonly ref struct ReadOnlySpan; + public static class MemoryExtensions { - public static implicit operator {{type}}(T[] array) + public static ReadOnlySpan AsSpan(string s) { Console.Write("{{output}}"); return default; @@ -655,49 +1820,42 @@ public readonly ref struct {{type}} .VerifyDiagnostics() .EmitToImageReference(); - var source = $$""" + var source = """ using System; - {{type}} s = arr(); - use(s); - static int[] arr() => new int[] { 1, 2, 3 }; - static void use({{type}} s) { } - """; - - var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); - var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); - verifier.VerifyDiagnostics( - // (2,1): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. - // Span s = arr(); - Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(2, 1), - // (5,17): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. - // static void use(Span s) { } - Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(5, 17), - // (5,41): warning CS0436: The type 'Span' in '' conflicts with the imported type 'Span' in 'Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in ''. - // public static implicit operator Span(T[] array) - Diagnostic(ErrorCode.WRN_SameFullNameThisAggAgg, $"{type}").WithArguments("", $"System.{type}", "Span1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", $"System.{type}").WithLocation(5, 41)); + ReadOnlySpan s = source(); + use(s); + static string source() => ""; + static void use(ReadOnlySpan s) { } + """; - verifier.VerifyIL("", $$""" + var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer", + // warning CS0436: Type conflicts with imported type + options: TestOptions.UnsafeReleaseExe.WithSpecificDiagnosticOptions("CS0436", ReportDiagnostic.Suppress)); + var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", """ { // Code size 16 (0x10) .maxstack 1 - IL_0000: call "int[] Program.<
$>g__arr|0_0()" - IL_0005: call "System.{{type}} System.{{type}}.op_Implicit(int[])" - IL_000a: call "void Program.<
$>g__use|0_1(System.{{type}})" + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000a: call "void Program.<
$>g__use|0_1(System.ReadOnlySpan)" IL_000f: ret } """); } - [Theory, CombinatorialData] - public void Conversion_Array_Span_Implicit_MultipleSpans_02( - [CombinatorialValues("Span", "ReadOnlySpan")] string type) + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_MultipleSpans_02() { - string getSpanSource(string output) => $$""" + static string getSpanSource(string output) => $$""" namespace System { - public readonly ref struct {{type}} + public readonly ref struct ReadOnlySpan; + public static class MemoryExtensions { - public static implicit operator {{type}}(T[] array) + public static ReadOnlySpan AsSpan(string s) { Console.Write("{{output}}"); return default; @@ -710,36 +1868,113 @@ public readonly ref struct {{type}} .VerifyDiagnostics() .EmitToImageReference(aliases: ["lib"]); - var source = $$""" + var source = """ extern alias lib; - lib::System.{{type}} s = arr(); - static int[] arr() => new int[] { 1, 2, 3 }; + lib::System.ReadOnlySpan s = source(); + static string source() => ""; """; var comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); var verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "External"); verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000a: pop + IL_000b: ret + } + """); - verifier.VerifyIL("", $$""" + source = """ + System.ReadOnlySpan s = source(); + static string source() => ""; + """; + + comp = CreateCompilation([source, getSpanSource("Internal")], [spanComp], assemblyName: "Consumer"); + verifier = CompileAndVerify(comp, verify: Verification.FailsILVerify, expectedOutput: "Internal"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ { // Code size 12 (0xc) .maxstack 1 - IL_0000: call "int[] Program.<
$>g__arr|0_0()" - IL_0005: call "System.{{type}} System.{{type}}.op_Implicit(int[])" + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_000a: pop IL_000b: ret } """); } - [Theory, CombinatorialData] - public void Conversion_Array_Span_Implicit_SemanticModel( - [CombinatorialValues("Span", "ReadOnlySpan")] string destination) + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_MultipleSpans_03() + { + var span1 = CreateCompilation(""" + namespace System; + public readonly ref struct ReadOnlySpan; + """, assemblyName: "Span1") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span1"]); + var span2 = CreateCompilation($$""" + extern alias span1; + namespace System; + public readonly ref struct ReadOnlySpan; + public static class MemoryExtensions + { + public static span1::System.ReadOnlySpan AsSpan(string s) + { + Console.Write("Span2"); + return default; + } + } + """, [span1], assemblyName: "Span2") + .VerifyDiagnostics() + .EmitToImageReference(aliases: ["span2"]); + + var source = """ + extern alias span1; + span1::System.ReadOnlySpan s = source(); + static string source() => ""; + """; + var comp = CreateCompilation(source, [span1, span2], assemblyName: "Consumer"); + var verifier = CompileAndVerify(comp, expectedOutput: "Span2"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: call "string Program.<
$>g__source|0_0()" + IL_0005: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000a: pop + IL_000b: ret + } + """); + + source = """ + extern alias span2; + span2::System.ReadOnlySpan s = source(); + static string source() => ""; + """; + CreateCompilation(source, [span2], assemblyName: "Consumer").VerifyDiagnostics( + // (2,38): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // span2::System.ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 38)); + } + + [Theory] + [InlineData("System.Int32[]", "System.Span")] + [InlineData("System.Int32[]", "System.ReadOnlySpan")] + [InlineData("System.Span", "System.ReadOnlySpan")] + [InlineData("System.ReadOnlySpan", "System.ReadOnlySpan")] + [InlineData("System.String", "System.ReadOnlySpan")] + public void Conversion_Span_Implicit_SemanticModel(string sourceType, string targetType) { var source = $$""" class C { - System.{{destination}} M(int[] arg) { return arg; } + {{targetType}} M({{sourceType}} arg) { return arg; } } """; @@ -751,8 +1986,8 @@ class C Assert.Equal("arg", arg!.ToString()); var argType = model.GetTypeInfo(arg); - Assert.Equal("System.Int32[]", argType.Type.ToTestDisplayString()); - Assert.Equal($"System.{destination}", argType.ConvertedType.ToTestDisplayString()); + Assert.Equal(sourceType, argType.Type.ToTestDisplayString()); + Assert.Equal(targetType, argType.ConvertedType.ToTestDisplayString()); var argConv = model.GetConversion(arg); Assert.Equal(ConversionKind.ImplicitSpan, argConv.Kind); @@ -799,36 +2034,333 @@ class C """); } - { - var cast = casts[1]; - Assert.Equal("(ReadOnlySpan)x", cast.ToString()); + { + var cast = casts[1]; + Assert.Equal("(ReadOnlySpan)x", cast.ToString()); + + var op = (IConversionOperation)model.GetOperation(cast)!; + var conv = op.GetConversion(); + Assert.Equal(ConversionKind.ExplicitSpan, conv.Kind); + + model.VerifyOperationTree(cast, """ + 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: + IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') + """); + } + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Implicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + class C + { + System.Span M(int[] arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (3,41): error CS0029: Cannot implicitly convert type 'int[]' to 'System.Span' + // System.Span M(int[] arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.Span").WithLocation(3, 41)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Explicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + Span M(int[] arg) => (Span)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,34): error CS0030: Cannot convert type 'int[]' to 'System.Span' + // Span M(int[] arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("int[]", "System.Span").WithLocation(4, 34)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_ReadOnlySpan_Implicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + class C + { + System.ReadOnlySpan M(int[] arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (3,49): error CS0029: Cannot implicitly convert type 'int[]' to 'System.ReadOnlySpan' + // System.ReadOnlySpan M(int[] arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.ReadOnlySpan").WithLocation(3, 49)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_ReadOnlySpan_Explicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(int[] arg) => (ReadOnlySpan)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,42): error CS0030: Cannot convert type 'int[]' to 'System.ReadOnlySpan' + // ReadOnlySpan M(int[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("int[]", "System.ReadOnlySpan").WithLocation(4, 42)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(Span arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,46): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M(Span arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(4, 46)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Explicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(Span arg) => (ReadOnlySpan)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,46): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(4, 46)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_Struct(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(Span arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics(); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_Struct_Generic(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan> M(Span> arg) => arg; + } + struct S; + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,55): error CS0029: Cannot implicitly convert type 'System.Span>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M(Span> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(4, 55)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_Struct_Generic_NullabilityAnnotation(LanguageVersion langVersion) + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M(Span> arg) => arg; + } + struct S; + """; + var targetType = langVersion > LanguageVersion.CSharp13 ? "System.Span>" : "System.ReadOnlySpan>"; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (5,56): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments(targetType, "System.ReadOnlySpan>").WithLocation(5, 56)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,54): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 54)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Explicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(ReadOnlySpan arg) => (ReadOnlySpan)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,54): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 54)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_Struct(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics(); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_Struct_Generic(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + } + struct S; + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,63): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(4, 63)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_Struct_Generic_NullabilityAnnotation(LanguageVersion langVersion) + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + } + struct S; + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (5,64): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(5, 64)); + } + + [Theory, CombinatorialData] + public void Conversion_ReadOnlySpan_Span_Implicit( + [CombinatorialLangVersions] LanguageVersion langVersion, + [CombinatorialValues("string", "object")] string type) + { + var source = $$""" + using System; + class C + { + Span<{{type}}> M(ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,49): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", $"System.Span<{type}>").WithLocation(4, 49)); + } + + [Theory, CombinatorialData] + public void Conversion_ReadOnlySpan_Span_Explicit( + [CombinatorialLangVersions] LanguageVersion langVersion, + [CombinatorialValues("string", "object")] string type) + { + var source = $$""" + using System; + class C + { + Span<{{type}}> M(ReadOnlySpan arg) => (Span<{{type}}>)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,49): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, $"(Span<{type}>)arg").WithArguments("System.ReadOnlySpan", $"System.Span<{type}>").WithLocation(4, 49)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_ReadOnlySpan_Implicit_UnrelatedElementType(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M(string arg) => arg; + } - var op = (IConversionOperation)model.GetOperation(cast)!; - var conv = op.GetConversion(); - Assert.Equal(ConversionKind.ExplicitSpan, conv.Kind); + namespace System + { + public readonly ref struct ReadOnlySpan; + } + """; + CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,42): error CS0029: Cannot implicitly convert type 'string' to 'System.ReadOnlySpan' + // ReadOnlySpan M(string arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("string", "System.ReadOnlySpan").WithLocation(4, 42)); + } - model.VerifyOperationTree(cast, """ - 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: - IParameterReferenceOperation: x (OperationKind.ParameterReference, Type: T[]) (Syntax: 'x') - """); - } + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_Span_Implicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + Span M(string arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,33): error CS0029: Cannot implicitly convert type 'string' to 'System.Span' + // Span M(string arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("string", "System.Span").WithLocation(4, 33)); } [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Implicit_UnrelatedElementType(LanguageVersion langVersion) + public void Conversion_string_Span_Explicit(LanguageVersion langVersion) { var source = """ + using System; class C { - System.Span M(int[] arg) => arg; + Span M(string arg) => (Span)arg; } """; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( - // (3,41): error CS0029: Cannot implicitly convert type 'int[]' to 'System.Span' - // System.Span M(int[] arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.Span").WithLocation(3, 41)); + // (4,33): error CS0030: Cannot convert type 'string' to 'System.Span' + // Span M(string arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("string", "System.Span").WithLocation(4, 33)); } [Fact] @@ -944,6 +2476,133 @@ class C CreateCompilationWithSpanAndMemoryExtensions(source).VerifyDiagnostics(expectedDiagnostics); } + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_NullableAnalysis() + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan M1(Span arg) => arg; + ReadOnlySpan M2(Span arg) => arg; + ReadOnlySpan M3(Span arg) => arg; + ReadOnlySpan M4(Span arg) => arg; + ReadOnlySpan M5(Span arg) => arg; + + ReadOnlySpan M6(Span arg) => (ReadOnlySpan)arg; + ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; + } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,51): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M2(Span arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(6, 51), + // (7,51): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M3(Span arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(7, 51), + // (9,51): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M5(Span arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(9, 51), + // (11,51): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 51), + // (12,51): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51)); + + var expectedDiagnostics = new[] + { + // (6,51): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M2(Span arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(6, 51), + // (9,51): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M5(Span arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(9, 51), + // (11,51): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(11, 51), + // (12,51): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_NullableAnalysis() + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan M1(ReadOnlySpan arg) => arg; + ReadOnlySpan M2(ReadOnlySpan arg) => arg; + ReadOnlySpan M3(ReadOnlySpan arg) => arg; + ReadOnlySpan M4(ReadOnlySpan arg) => arg; + ReadOnlySpan M5(ReadOnlySpan arg) => arg; + + ReadOnlySpan M6(ReadOnlySpan arg) => (ReadOnlySpan)arg; + ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; + } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M2(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(6, 59), + // (7,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M3(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(7, 59), + // (9,59): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M5(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(9, 59), + // (11,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 59), + // (12,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59)); + + var expectedDiagnostics = new[] + { + // (6,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M2(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(6, 59), + // (9,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M5(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(9, 59), + // (11,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M6(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 59), + // (12,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_ReadOnlySpan_Implicit_NullableAnalysis(LanguageVersion langVersion) + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan M1(string arg) => arg; + ReadOnlySpan M2(string? arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics(); + } + [Fact] public void Conversion_Array_Span_Implicit_NullableAnalysis_NestedArrays() { @@ -1108,6 +2767,99 @@ string targetType(string inner) => langVersion > LanguageVersion.CSharp13 ? $"System.ReadOnlySpan>" : $"S<{inner}>[]"; } + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability() + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M1(Span> arg) => arg; + ReadOnlySpan> M2(Span> arg) => arg; + ReadOnlySpan> M3(Span> arg) => arg; + ReadOnlySpan> M4(Span> arg) => arg; + ReadOnlySpan> M5(Span> arg) => arg; + + ReadOnlySpan> M6(Span> arg) => (ReadOnlySpan>)arg; + ReadOnlySpan> M7(Span> arg) => (ReadOnlySpan>)arg; + } + interface I { } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,57): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M2(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(6, 57), + // (7,57): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M3(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(7, 57), + // (9,57): error CS0029: Cannot implicitly convert type 'System.Span>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M5(Span> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(9, 57), + // (11,57): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M6(Span> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan>)arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(11, 57), + // (12,57): error CS0030: Cannot convert type 'System.Span>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M7(Span> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(12, 57)); + + var expectedDiagnostics = new[] + { + // (6,57): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M2(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(6, 57), + // (9,57): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M5(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(9, 57), + // (11,57): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M6(Span> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan>)arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(11, 57), + // (12,57): error CS0030: Cannot convert type 'System.Span>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M7(Span> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(12, 57) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability(LanguageVersion langVersion) + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M1(ReadOnlySpan> arg) => arg; + ReadOnlySpan> M2(ReadOnlySpan> arg) => arg; + ReadOnlySpan> M3(ReadOnlySpan> arg) => arg; + ReadOnlySpan> M4(ReadOnlySpan> arg) => arg; + ReadOnlySpan> M5(ReadOnlySpan> arg) => arg; + + ReadOnlySpan> M6(ReadOnlySpan> arg) => (ReadOnlySpan>)arg; + ReadOnlySpan> M7(ReadOnlySpan> arg) => (ReadOnlySpan>)arg; + } + struct S { } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (6,65): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M2(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(6, 65), + // (7,65): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M3(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(7, 65), + // (9,65): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M5(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(9, 65), + // (11,65): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M6(ReadOnlySpan> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan>)arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(11, 65), + // (12,65): error CS0030: Cannot convert type 'System.ReadOnlySpan>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M7(ReadOnlySpan> arg) => (ReadOnlySpan>)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan>)arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(12, 65)); + } + [Theory, MemberData(nameof(LangVersions))] public void Conversion_Array_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability_Covariant(LanguageVersion langVersion) { @@ -1127,6 +2879,64 @@ interface I { } Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("I[]", targetType).WithLocation(5, 52)); } + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability_Covariant() + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M(Span> arg) => arg; + } + interface I { } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (5,56): error CS0029: Cannot implicitly convert type 'System.Span>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M(Span> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(5, 56)); + + var expectedDiagnostics = new[] + { + // (5,56): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M(Span> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(5, 56) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability_Covariant() + { + var source = """ + #nullable enable + using System; + class C + { + ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + } + interface I { } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (5,64): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan>' to 'System.ReadOnlySpan>' + // ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(5, 64)); + + var expectedDiagnostics = new[] + { + // (5,64): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan>' doesn't match target type 'ReadOnlySpan>'. + // ReadOnlySpan> M(ReadOnlySpan> arg) => arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(5, 64) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + [Theory, MemberData(nameof(LangVersions))] public void Conversion_Array_Span_Implicit_NullableAnalysis_Outer(LanguageVersion langVersion) { @@ -1181,6 +2991,71 @@ static void M4(this ReadOnlySpan arg) { } Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "b").WithArguments("string?[]", "System.ReadOnlySpan", "arg", "void C.M3(ReadOnlySpan arg)").WithLocation(9, 17)); } + [Fact] + public void Conversion_Span_ReadOnlySpan_Implicit_NullableAnalysis_ExtensionMethodReceiver() + { + var source = """ + #nullable enable + using System; + static class C + { + static void MS(Span a, Span b) + { + a.M1(); b.M1(); + a.M2(); b.M2(); + } + static void M1(this ReadOnlySpan arg) { } + static void M2(this ReadOnlySpan arg) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source).VerifyDiagnostics( + // (7,17): warning CS8620: Argument of type 'Span' cannot be used for parameter 'arg' of type 'ReadOnlySpan' in 'void C.M1(ReadOnlySpan arg)' due to differences in the nullability of reference types. + // a.M1(); b.M1(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "b").WithArguments("System.Span", "System.ReadOnlySpan", "arg", "void C.M1(ReadOnlySpan arg)").WithLocation(7, 17)); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_NullableAnalysis_ExtensionMethodReceiver() + { + var source = """ + #nullable enable + using System; + static class C + { + static void MS(ReadOnlySpan a, ReadOnlySpan b) + { + a.M1(); b.M1(); + a.M2(); b.M2(); + } + static void M1(this ReadOnlySpan arg) { } + static void M2(this ReadOnlySpan arg) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (7,17): warning CS8620: Argument of type 'ReadOnlySpan' cannot be used for parameter 'arg' of type 'ReadOnlySpan' in 'void C.M1(ReadOnlySpan arg)' due to differences in the nullability of reference types. + // a.M1(); b.M1(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "b").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan", "arg", "void C.M1(ReadOnlySpan arg)").WithLocation(7, 17)); + } + + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_NullableAnalysis_ExtensionMethodReceiver() + { + var source = """ + #nullable enable + using System; + static class C + { + static void MS(string a, string? b) + { + a.M(); + b.M(); + } + static void M(this ReadOnlySpan arg) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(); + } + [Theory, CombinatorialData] public void Conversion_Array_Span_Implicit_Ref_01( [CombinatorialValues("ref", "ref readonly", "in")] string modifier) @@ -1348,34 +3223,82 @@ .locals init (System.Span V_0, //s IL_0018: stloc.1 IL_0019: ret } - """); + """); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Opposite_Implicit(LanguageVersion langVersion) + { + var source = """ + class C + { + int[] M1(System.Span arg) => arg; + int[] M2(System.ReadOnlySpan arg) => arg; + object[] M3(System.ReadOnlySpan arg) => arg; + string[] M4(System.ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (3,40): error CS0029: Cannot implicitly convert type 'System.Span' to 'int[]' + // int[] M1(System.Span arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "int[]").WithLocation(3, 40), + // (4,48): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'int[]' + // int[] M2(System.ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "int[]").WithLocation(4, 48), + // (5,54): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'object[]' + // object[] M3(System.ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "object[]").WithLocation(5, 54), + // (6,54): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'string[]' + // string[] M4(System.ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 54)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Opposite_Implicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + Span M1(ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,45): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M1(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 45)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Opposite_Implicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M1(ReadOnlySpan arg) => arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,59): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M1(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 59)); } [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Opposite_Implicit(LanguageVersion langVersion) + public void Conversion_string_ReadOnlySpan_Opposite_Implicit(LanguageVersion langVersion) { var source = """ + using System; class C { - int[] M1(System.Span arg) => arg; - int[] M2(System.ReadOnlySpan arg) => arg; - object[] M3(System.ReadOnlySpan arg) => arg; - string[] M4(System.ReadOnlySpan arg) => arg; + string M1(ReadOnlySpan arg) => arg; } """; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( - // (3,40): error CS0029: Cannot implicitly convert type 'System.Span' to 'int[]' - // int[] M1(System.Span arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "int[]").WithLocation(3, 40), - // (4,48): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'int[]' - // int[] M2(System.ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "int[]").WithLocation(4, 48), - // (5,54): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'object[]' - // object[] M3(System.ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "object[]").WithLocation(5, 54), - // (6,54): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'string[]' - // string[] M4(System.ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 54)); + // (4,43): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'string' + // string M1(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 43)); } [Theory, MemberData(nameof(LangVersions))] @@ -1405,6 +3328,54 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string[])arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 54)); } + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Opposite_Explicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + Span M1(ReadOnlySpan arg) => (Span)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,45): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M1(ReadOnlySpan arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 45)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Opposite_Explicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + ReadOnlySpan M1(ReadOnlySpan arg) => (ReadOnlySpan)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M1(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 59)); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_ReadOnlySpan_Opposite_Explicit(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + string M1(ReadOnlySpan arg) => (string)arg; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,43): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'string' + // string M1(ReadOnlySpan arg) => (string)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string)arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 43)); + } + [Fact] public void Conversion_Array_Span_Opposite_Explicit_UserDefined() { @@ -1446,7 +3417,90 @@ .maxstack 1 } [Fact] - public void Conversion_Array_Span_Implicit_Params() + public void Conversion_Span_ReadOnlySpan_Opposite_Explicit_UserDefined() + { + var source = """ + using System; + class C + { + Span M(ReadOnlySpan arg) => (Span)arg; + } + + namespace System + { + readonly ref struct Span; + readonly ref struct ReadOnlySpan + { + public static explicit operator Span(ReadOnlySpan span) => throw null; + } + } + """; + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.Span System.ReadOnlySpan.op_Explicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,44): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 44) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_string_ReadOnlySpan_Opposite_Explicit_UserDefined() + { + var source = """ + using System; + class C + { + string M(ReadOnlySpan arg) => (string)arg; + } + + namespace System + { + readonly ref struct ReadOnlySpan + { + public static explicit operator string(ReadOnlySpan span) => throw null; + } + } + """; + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "string System.ReadOnlySpan.op_Explicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,42): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'string' + // string M(ReadOnlySpan arg) => (string)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string)arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 42) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Implicit_Params(LanguageVersion langVersion) { var source = """ using System; @@ -1464,7 +3518,7 @@ void M2(params ReadOnlySpan s) { } void M3(params string[] s) { } } """; - var comp = CreateCompilationWithSpanAndMemoryExtensions(source); + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); var verifier = CompileAndVerify(comp).VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -1505,36 +3559,220 @@ void M2(params ReadOnlySpan s) { } void M3(params object[] p) { } } """; - var comp = CreateCompilationWithSpanAndMemoryExtensions(source); - var verifier = CompileAndVerify(comp).VerifyDiagnostics(); - verifier.VerifyIL("C.M", """ + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 38 (0x26) + .maxstack 2 + .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 + } + """); + + var expectedIl = """ + { + // Code size 45 (0x2d) + .maxstack 5 + .locals init (object[] V_0) + IL_0000: ldarg.0 + 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 + } + """; + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_Params(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + void M(Span a) + { + M1(a); + } + void M1(params ReadOnlySpan s) { } + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0007: call "void C.M1(params System.ReadOnlySpan)" + IL_000c: ret + } + """); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_Params() + { + var source = """ + using System; + class C + { + void M(Span a) + { + M1(a); + } + void M1(params ReadOnlySpan s) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,12): error CS1503: Argument 1: cannot convert from 'System.Span' to 'object' + // M1(a); + Diagnostic(ErrorCode.ERR_BadArgType, "a").WithArguments("1", "System.Span", "object").WithLocation(6, 12)); + + var expectedIl = """ + { + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0007: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000c: call "void C.M1(params System.ReadOnlySpan)" + IL_0011: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_CastUp_Implicit_Params() + { + var source = """ + using System; + class C + { + void M(ReadOnlySpan a) + { + M1(a); + } + void M1(params ReadOnlySpan s) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,12): error CS1503: Argument 1: cannot convert from 'System.ReadOnlySpan' to 'object' + // M1(a); + Diagnostic(ErrorCode.ERR_BadArgType, "a").WithArguments("1", "System.ReadOnlySpan", "object").WithLocation(6, 12)); + + var expectedIl = """ + { + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0007: call "void C.M1(params System.ReadOnlySpan)" + IL_000c: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_string_ReadOnlySpan_CastUp_Implicit_Params() + { + var source = """ + using System; + class C + { + void M(ReadOnlySpan a) + { + M1(a); + } + void M1(params ReadOnlySpan s) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (6,12): error CS1503: Argument 1: cannot convert from 'System.ReadOnlySpan' to 'object' + // M1(a); + Diagnostic(ErrorCode.ERR_BadArgType, "a").WithArguments("1", "System.ReadOnlySpan", "object").WithLocation(6, 12)); + + var expectedIl = """ { - // Code size 45 (0x2d) - .maxstack 5 - .locals init (object[] V_0) + // Code size 13 (0xd) + .maxstack 2 IL_0000: ldarg.0 - 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 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0007: call "void C.M1(params System.ReadOnlySpan)" + IL_000c: ret } - """); + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); } [Theory, MemberData(nameof(LangVersions))] @@ -2113,6 +4351,123 @@ static class C Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "default").WithArguments(type).WithLocation(4, 15)); } + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Implicit_ExpressionTree(LanguageVersion langVersion) + { + var source = """ + using System; + using System.Linq.Expressions; + + C.R(x => C.M(x)); + + delegate void D(Span x); + + static class C + { + public static void R(Expression e) { } + public static void M(ReadOnlySpan x) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + // (4,5): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'Span'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("Span").WithLocation(4, 5), + // (4,14): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'Span'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("Span").WithLocation(4, 14)); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_CastUp_Implicit_ExpressionTree() + { + var source = """ + using System; + using System.Linq.Expressions; + + C.R(x => C.M(x)); + + delegate void D(Span x); + + static class C + { + public static void R(Expression e) { } + public static void M(ReadOnlySpan x) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (4,14): error CS1503: Argument 1: cannot convert from 'System.Span' to 'System.ReadOnlySpan' + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "System.Span", "System.ReadOnlySpan").WithLocation(4, 14)); + + var expectedDiagnostics = new[] + { + // (4,5): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'Span'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("Span").WithLocation(4, 5), + // (4,14): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'Span'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("Span").WithLocation(4, 14) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_ExpressionTree() + { + var source = """ + using System; + using System.Linq.Expressions; + + C.R(x => C.M(x)); + + delegate void D(ReadOnlySpan x); + + static class C + { + public static void R(Expression e) { } + public static void M(ReadOnlySpan x) { } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (4,14): error CS1503: Argument 1: cannot convert from 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 14)); + + var expectedDiagnostics = new[] + { + // (4,5): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("ReadOnlySpan").WithLocation(4, 5), + // (4,14): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. + // C.R(x => C.M(x)); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, "x").WithArguments("ReadOnlySpan").WithLocation(4, 14) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); + } + + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_ReadOnlySpan_Implicit_ExpressionTree(LanguageVersion langVersion) + { + var source = """ + using System; + using System.Linq.Expressions; + + C.R(x => C.M(x)); + + static class C + { + public static void R(Expression> e) => e.Compile()("s"); + public static void M(ReadOnlySpan x) => Console.Write(x.Length + " " + x[0]); + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); + CompileAndVerify(comp, expectedOutput: "1 s").VerifyDiagnostics(); + } + [Theory, CombinatorialData] public void Conversion_Array_ReadOnlySpan_Covariant( [CombinatorialLangVersions] LanguageVersion langVersion, @@ -2451,93 +4806,273 @@ class C {{constraints}} Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)x").WithArguments("T[]", "System.Span").WithLocation(7, 26)); } - [Theory, MemberData(nameof(LangVersions))] - public void Conversion_Array_Span_Variance_02(LanguageVersion langVersion) + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Variance_02(LanguageVersion langVersion) + { + var source = """ + using System; + class C + where T : class + where U : class, T + { + ReadOnlySpan F1(T[] x) => x; + ReadOnlySpan F2(T[] x) => (ReadOnlySpan)x; + Span F3(T[] x) => x; + Span F4(T[] x) => (Span)x; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(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), + // (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)); + } + + [Fact] + public void Conversion_Array_Span_Variance_03() + { + var source = """ + using System; + class C + where T : class, U + where U : class + { + ReadOnlySpan F1(T[] x) => x; + ReadOnlySpan F2(T[] x) => (ReadOnlySpan)x; + Span F3(T[] x) => x; + Span F4(T[] x) => (Span)x; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).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) + }; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilationWithSpanAndMemoryExtensions(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_Variance() + { + var source = """ + using System; + class C + where T : class + where U : class, T + { + ReadOnlySpan F1(Span x) => x; + ReadOnlySpan F2(Span x) => (ReadOnlySpan)x; + ReadOnlySpan F3(Span x) => x; + ReadOnlySpan F4(Span x) => (ReadOnlySpan)x; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (6,38): error CS0029: Cannot implicitly convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan F1(Span x) => x; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(6, 38), + // (7,38): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan F2(Span x) => (ReadOnlySpan)x; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)x").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(7, 38)); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Variance() + { + var source = """ + using System; + class C + where T : class + where U : class, T + { + ReadOnlySpan F1(ReadOnlySpan x) => x; + ReadOnlySpan F2(ReadOnlySpan x) => (ReadOnlySpan)x; + ReadOnlySpan F3(ReadOnlySpan x) => x; + ReadOnlySpan F4(ReadOnlySpan x) => (ReadOnlySpan)x; + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (6,46): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan F1(ReadOnlySpan x) => x; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(6, 46), + // (7,46): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan F2(ReadOnlySpan x) => (ReadOnlySpan)x; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)x").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(7, 46)); + } + + [Theory, CombinatorialData] + public void Conversion_Array_Span_ThroughUserImplicit( + [CombinatorialValues("Span", "ReadOnlySpan")] string destination) + { + var source = $$""" + using System; + + D.M(new C()); + + class C + { + public static implicit 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); + } + } + } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.Span' + // D.M(new C()); + Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", $"System.{destination}").WithLocation(3, 5)); + + var expectedOutput = "456"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_ThroughUserImplicit() + { + var source = """ + using System; + + D.M(new C()); + + class C + { + public static implicit operator Span(C c) => new Span(new int[] { 4, 5, 6 }); + } + + static class D + { + public static void M(ReadOnlySpan xs) => Console.Write(xs.Length + " " + xs[0]); + } + """; + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.ReadOnlySpan' + // D.M(new C()); + Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", "System.ReadOnlySpan").WithLocation(3, 5)); + + var expectedOutput = "3 4"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_CastUp_ThroughUserImplicit() { var source = """ using System; - class C - where T : class - where U : class, T + + D.M(new C()); + + class C { - ReadOnlySpan F1(T[] x) => x; - ReadOnlySpan F2(T[] x) => (ReadOnlySpan)x; - Span F3(T[] x) => x; - Span F4(T[] x) => (Span)x; + public static implicit operator Span(C c) => new Span(new string[] { "x" }); + } + + static class D + { + public static void M(ReadOnlySpan xs) => Console.Write(xs.Length + " " + xs[0]); } """; - CreateCompilationWithSpanAndMemoryExtensions(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), - // (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)); + + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.ReadOnlySpan' + // D.M(new C()); + Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", "System.ReadOnlySpan").WithLocation(3, 5)); + + var expectedOutput = "1 x"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); } [Fact] - public void Conversion_Array_Span_Variance_03() + public void Conversion_ReadOnlySpan_ReadOnlySpan_ThroughUserImplicit() { var source = """ using System; - class C - where T : class, U - where U : class + + D.M(new C()); + + class C { - ReadOnlySpan F1(T[] x) => x; - ReadOnlySpan F2(T[] x) => (ReadOnlySpan)x; - Span F3(T[] x) => x; - Span F4(T[] x) => (Span)x; + public static implicit operator ReadOnlySpan(C c) => new ReadOnlySpan(new string[] { "x" }); + } + + static class D + { + public static void M(ReadOnlySpan xs) => Console.Write(xs.Length + " " + xs[0]); } """; - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics(); - // Note: although a breaking change, the previous would fail with a runtime exception - // (Span's constructor checks that the element types are identical). + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.ReadOnlySpan' + // D.M(new C()); + Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", "System.ReadOnlySpan").WithLocation(3, 5)); - 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) - }; + var expectedOutput = "1 x"; - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilationWithSpanAndMemoryExtensions(source).VerifyDiagnostics(expectedDiagnostics); + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.FailsILVerify).VerifyDiagnostics(); } - [Theory, CombinatorialData] - public void Conversion_Array_Span_ThroughUserImplicit( - [CombinatorialValues("Span", "ReadOnlySpan")] string destination) + [Fact] + public void Conversion_string_ReadOnlySpan_ThroughUserImplicit() { - var source = $$""" + var source = """ using System; D.M(new C()); class C { - public static implicit operator int[](C c) => new int[] { 4, 5, 6 }; + public static implicit operator string(C c) => "x"; } static class D { - public static void M({{destination}} xs) - { - foreach (var x in xs) - { - Console.Write(x); - } - } + public static void M(ReadOnlySpan xs) => Console.Write(xs.Length + " " + xs[0]); } """; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( - // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.Span' + // (3,5): error CS1503: Argument 1: cannot convert from 'C' to 'System.ReadOnlySpan' // D.M(new C()); - Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", $"System.{destination}").WithLocation(3, 5)); + Diagnostic(ErrorCode.ERR_BadArgType, "new C()").WithArguments("1", "C", "System.ReadOnlySpan").WithLocation(3, 5)); - var expectedOutput = "456"; + var expectedOutput = "1 x"; var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); @@ -2800,7 +5335,172 @@ .maxstack 1 var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); verifier.VerifyIL("C.M", expectedIl); - comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_ExtensionMethodReceiver_Implicit() + { + var source = """ + using System; + + C.M(new Span(new int[] { 7, 8, 9 })); + + static class C + { + public static void M(Span arg) => arg.E(); + public static void E(this ReadOnlySpan arg) => Console.Write(arg[1]); + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (7,44): error CS1929: 'Span' does not contain a definition for 'E' and the best extension method overload 'C.E(ReadOnlySpan)' requires a receiver of type 'System.ReadOnlySpan' + // public static void M(Span arg) => arg.E(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "arg").WithArguments("System.Span", "E", "C.E(System.ReadOnlySpan)", "System.ReadOnlySpan").WithLocation(7, 44)); + + var expectedOutput = "8"; + + var expectedIl = """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0006: call "void C.E(System.ReadOnlySpan)" + IL_000b: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_Span_ReadOnlySpan_CastUp_ExtensionMethodReceiver_Implicit() + { + var source = """ + using System; + + C.M(new Span(new string[] { "x", "y" })); + + static class C + { + public static void M(Span arg) => arg.E(); + public static void E(this ReadOnlySpan arg) => Console.Write(arg[1]); + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (7,47): error CS1929: 'Span' does not contain a definition for 'E' and the best extension method overload 'C.E(ReadOnlySpan)' requires a receiver of type 'System.ReadOnlySpan' + // public static void M(Span arg) => arg.E(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "arg").WithArguments("System.Span", "E", "C.E(System.ReadOnlySpan)", "System.ReadOnlySpan").WithLocation(7, 47)); + + var expectedOutput = "y"; + + var expectedIl = """ + { + // Code size 17 (0x11) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.Span.op_Implicit(System.Span)" + IL_0006: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_000b: call "void C.E(System.ReadOnlySpan)" + IL_0010: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_ExtensionMethodReceiver_Implicit() + { + var source = """ + using System; + + C.M(new ReadOnlySpan(new string[] { "x", "y" })); + + static class C + { + public static void M(ReadOnlySpan arg) => arg.E(); + public static void E(this ReadOnlySpan arg) => Console.Write(arg[1]); + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (7,55): error CS1929: 'ReadOnlySpan' does not contain a definition for 'E' and the best extension method overload 'C.E(ReadOnlySpan)' requires a receiver of type 'System.ReadOnlySpan' + // public static void M(ReadOnlySpan arg) => arg.E(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "arg").WithArguments("System.ReadOnlySpan", "E", "C.E(System.ReadOnlySpan)", "System.ReadOnlySpan").WithLocation(7, 55)); + + var expectedOutput = "y"; + + var expectedIl = """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0006: call "void C.E(System.ReadOnlySpan)" + IL_000b: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + } + + [Fact] + public void Conversion_string_ReadOnlySpan_ExtensionMethodReceiver_Implicit() + { + var source = """ + using System; + + C.M("xyz"); + + static class C + { + public static void M(string arg) => arg.E(); + public static void E(this ReadOnlySpan arg) => Console.Write(arg[1]); + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( + // (7,41): error CS1929: 'string' does not contain a definition for 'E' and the best extension method overload 'C.E(ReadOnlySpan)' requires a receiver of type 'System.ReadOnlySpan' + // public static void M(string arg) => arg.E(); + Diagnostic(ErrorCode.ERR_BadInstanceArgType, "arg").WithArguments("string", "E", "C.E(System.ReadOnlySpan)", "System.ReadOnlySpan").WithLocation(7, 41)); + + var expectedOutput = "y"; + + var expectedIl = """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: call "void C.E(System.ReadOnlySpan)" + IL_000b: ret + } + """; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext); + var verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + verifier.VerifyIL("C.M", expectedIl); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source); verifier = CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); verifier.VerifyIL("C.M", expectedIl); } @@ -3440,6 +6140,46 @@ .locals init (object[] V_0) verifier.VerifyIL("C.M", expectedIl); } + [Theory, CombinatorialData] + public void Conversion_Span_ReadOnlySpan_RefSafety(bool cast) + { + var source = $$""" + using System; + class C + { + ReadOnlySpan M() + { + Span x = {{(cast ? "(Span)" : "")}}["a"]; + return x; + } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (7,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope + // return x; + Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 16)); + } + + [Theory, CombinatorialData] + public void Conversion_ReadOnlySpan_ReadOnlySpan_RefSafety(bool cast) + { + var source = $$""" + using System; + class C + { + ReadOnlySpan M() + { + ReadOnlySpan x = {{(cast ? "(ReadOnlySpan)" : "")}}["a"]; + return x; + } + } + """; + CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics( + // (7,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope + // return x; + Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 16)); + } + [Fact] public void OverloadResolution_SpanVsIEnumerable() { @@ -3717,11 +6457,11 @@ public void OverloadResolution_ReadOnlySpanVsArray_ExpressionTree_04(LanguageVer var source = """ using System; using System.Linq.Expressions; - + C.R(() => C.M(null)); C.R(() => C.M(default)); C.R(() => C.M(default(object[]))); - + static class C { public static void R(Expression e) => e.Compile()(); @@ -4102,8 +6842,8 @@ static class C CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } - [Theory, MemberData(nameof(LangVersions))] - public void OverloadResolution_SpanVsReadOnlySpan_03(LanguageVersion langVersion) + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_03() { var source = """ using System; @@ -4113,17 +6853,25 @@ public void OverloadResolution_SpanVsReadOnlySpan_03(LanguageVersion langVersion static class C { - public static void M(Span arg) { } - public static void M(ReadOnlySpan arg) { } + public static void M(Span arg) => Console.Write(1); + public static void M(ReadOnlySpan arg) => Console.Write(2); } """; - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( // (3,5): error CS1503: Argument 1: cannot convert from 'System.Span' to 'System.Span' // C.M(default(Span)); Diagnostic(ErrorCode.ERR_BadArgType, "default(Span)").WithArguments("1", "System.Span", "System.Span").WithLocation(3, 5), // (4,5): error CS1503: Argument 1: cannot convert from 'System.ReadOnlySpan' to 'System.Span' // C.M(default(ReadOnlySpan)); Diagnostic(ErrorCode.ERR_BadArgType, "default(ReadOnlySpan)").WithArguments("1", "System.ReadOnlySpan", "System.Span").WithLocation(4, 5)); + + var expectedOutput = "22"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } [Fact] @@ -4205,8 +6953,8 @@ static class C CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } - [Theory, MemberData(nameof(LangVersions))] - public void OverloadResolution_SpanVsReadOnlySpan_ExtensionMethodReceiver_04(LanguageVersion langVersion) + [Fact] + public void OverloadResolution_SpanVsReadOnlySpan_ExtensionMethodReceiver_04() { var source = """ using System; @@ -4216,11 +6964,11 @@ public void OverloadResolution_SpanVsReadOnlySpan_ExtensionMethodReceiver_04(Lan static class C { - public static void E(this Span arg) { } - public static void E(this ReadOnlySpan arg) { } + public static void E(this Span arg) => Console.Write(1); + public static void E(this ReadOnlySpan arg) => Console.Write(2); } """; - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)).VerifyDiagnostics( + CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( // (3,1): error CS1929: 'Span' does not contain a definition for 'E' and the best extension method overload 'C.E(Span)' requires a receiver of type 'System.Span' // default(Span).E(); Diagnostic(ErrorCode.ERR_BadInstanceArgType, "default(Span)").WithArguments("System.Span", "E", "C.E(System.Span)", "System.Span").WithLocation(3, 1), @@ -4228,7 +6976,13 @@ public static void E(this ReadOnlySpan arg) { } // default(ReadOnlySpan).E(); Diagnostic(ErrorCode.ERR_BadInstanceArgType, "default(ReadOnlySpan)").WithArguments("System.ReadOnlySpan", "E", "C.E(System.Span)", "System.Span").WithLocation(4, 1)); - // PROTOTYPE: Should work in C# 13 when ROS->ROS conversion is implemented. + var expectedOutput = "22"; + + var comp = CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); + + comp = CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90); + CompileAndVerify(comp, expectedOutput: expectedOutput).VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs index 4f5656dafe83c..66c7ffdcd12cd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs @@ -645,7 +645,7 @@ .maxstack 4 .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_1 IL_000d: ldc.i4.4 @@ -683,7 +683,7 @@ .maxstack 4 .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_1 IL_000d: ldc.i4.4 @@ -725,7 +725,7 @@ .maxstack 4 .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_1 IL_000d: ldc.i4.4 @@ -770,7 +770,7 @@ .maxstack 4 .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldloca.s V_1 IL_000d: ldc.i4.4 @@ -820,7 +820,7 @@ .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1, bool V_2) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.4 IL_000c: ldc.i4.4 @@ -862,7 +862,7 @@ .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1, bool V_2) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.4 IL_000c: ldc.i4.4 @@ -911,7 +911,7 @@ .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1, bool V_2) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.4 IL_000c: ldc.i4.4 @@ -957,7 +957,7 @@ .locals init (System.ReadOnlySpan V_0, //a System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_1, bool V_2) IL_0000: ldstr ""1"" - IL_0005: call ""System.ReadOnlySpan string.op_Implicit(string)"" + IL_0005: call ""System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)"" IL_000a: stloc.0 IL_000b: ldc.i4.4 IL_000c: ldc.i4.4 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 1024cb2514c2a..5b0e41ef03aa2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -3721,15 +3721,9 @@ public Example() {} // (5,38): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Field = stackalloc int[512]; Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 38), - // (5,38): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Field = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 38), // (6,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(6, 50), - // (6,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(6, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(6, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3808,10 +3802,7 @@ public Example() {} Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), - // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3838,10 +3829,7 @@ record struct Example() Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), - // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); } [WorkItem(60568, "https://github.com/dotnet/roslyn/issues/60568")] @@ -3868,10 +3856,7 @@ class Example Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "ReadOnlySpan").WithArguments("System.ReadOnlySpan").WithLocation(5, 12), // (5,50): error CS8353: A result of a stackalloc expression of type 'Span' cannot be used in this context because it may be exposed outside of the containing method // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50), - // (5,50): error CS8347: Cannot use a result of 'Span.implicit operator ReadOnlySpan(Span)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope - // public ReadOnlySpan Property { get; } = stackalloc int[512]; - Diagnostic(ErrorCode.ERR_EscapeCall, "stackalloc int[512]").WithArguments("System.Span.implicit operator System.ReadOnlySpan(System.Span)", "span").WithLocation(5, 50)); + Diagnostic(ErrorCode.ERR_EscapeStackAlloc, "stackalloc int[512]").WithArguments("System.Span").WithLocation(5, 50)); } [ConditionalFact(typeof(CoreClrOnly))] // For conversion from Span to ReadOnlySpan. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs index 37475d8c100c6..6a478e8fe103a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -3277,13 +3278,13 @@ ReadOnlySpan Test() var node = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single().Expression; var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.ReadOnlySpan System.String.op_Implicit(System.String? value)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(node); Assert.Equal("System.String", typeInfo.Type.ToTestDisplayString()); Assert.Equal("System.ReadOnlySpan", typeInfo.ConvertedType.ToTestDisplayString()); - Assert.True(model.GetConversion(node).IsUserDefined); + Assert.True(model.GetConversion(node).IsSpan); } [ConditionalFact(typeof(CoreClrOnly))] @@ -3305,13 +3306,14 @@ ReadOnlySpan Test() var node = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single().Expression; var symbolInfo = model.GetSymbolInfo(node); - Assert.Equal("System.ReadOnlySpan System.String.op_Implicit(System.String? value)", symbolInfo.Symbol.ToTestDisplayString()); + Assert.Null(symbolInfo.Symbol); var typeInfo = model.GetTypeInfo(node); Assert.Equal("System.ReadOnlySpan", typeInfo.Type.ToTestDisplayString()); Assert.Equal("System.ReadOnlySpan", typeInfo.ConvertedType.ToTestDisplayString()); Assert.True(model.GetConversion(node).IsIdentity); + Assert.True(((IConversionOperation)model.GetOperation(node)).GetConversion().IsSpan); } [Theory] diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index f22c1902567b0..85feb4e5a2da5 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -402,5 +402,9 @@ public static class WellKnownMemberNames internal const string LockTypeName = "Lock"; internal const string EnterScopeMethodName = "EnterScope"; internal const string LockScopeTypeName = "Scope"; + + internal const string CastUpMethodName = "CastUp"; + internal const string MemoryExtensionsTypeFullName = "System.MemoryExtensions"; + internal const string AsSpanMethodName = "AsSpan"; } } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index fc8157feeaa3f..b187e0dcd877e 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -2357,6 +2357,24 @@ protected static CSharpCompilation CreateCompilationWithSpanAndMemoryExtensions( } } + protected static CSharpCompilation CreateCompilationWithIndexAndRangeAndSpanAndMemoryExtensions(CSharpTestSource text, CSharpCompilationOptions options = null, CSharpParseOptions parseOptions = null, TargetFramework targetFramework = TargetFramework.NetCoreApp) + { + if (ExecutionConditionUtil.IsCoreClr) + { + return CreateCompilation(text, targetFramework: targetFramework, options: options, parseOptions: parseOptions); + } + else + { + var reference = CreateCompilation(new[] { TestSources.Index, TestSources.Range, TestSources.Span, TestSources.MemoryExtensions }, options: TestOptions.UnsafeReleaseDll).VerifyDiagnostics(); + + return CreateCompilation( + text, + references: new List() { reference.EmitToImageReference() }, + options: options, + parseOptions: parseOptions); + } + } + internal static string GetIdForErrorCode(ErrorCode code) { return MessageProvider.Instance.GetIdForErrorCode((int)code); diff --git a/src/Compilers/Test/Utilities/CSharp/TestSources.cs b/src/Compilers/Test/Utilities/CSharp/TestSources.cs index 31e4e082c12a0..7af0695589dde 100644 --- a/src/Compilers/Test/Utilities/CSharp/TestSources.cs +++ b/src/Compilers/Test/Utilities/CSharp/TestSources.cs @@ -32,10 +32,10 @@ public Span(T[] arr) this.arr = arr; this.Length = arr is null ? 0 : arr.Length; } - + public Span(T[] arr, int start, int length) { - if (start + length > arr.Length) + if (start + length > arr?.Length) { throw new ArgumentOutOfRangeException(); } @@ -117,10 +117,10 @@ public ReadOnlySpan(T[] arr) this.arr = arr; this.Length = arr is null ? 0 : arr.Length; } - + public ReadOnlySpan(T[] arr, int start, int length) { - if (start + length > arr.Length) + if (start + length > arr?.Length) { throw new ArgumentOutOfRangeException(); } @@ -180,6 +180,13 @@ public ref readonly T Current public static implicit operator ReadOnlySpan(string stringValue) => string.IsNullOrEmpty(stringValue) ? default : new ReadOnlySpan((T[])(object)stringValue.ToCharArray()); public ReadOnlySpan Slice(int offset, int length) => new ReadOnlySpan(this.arr, offset, length); + +#nullable enable + public static ReadOnlySpan CastUp(ReadOnlySpan items) where TDerived : class?, T + { + return new ReadOnlySpan(items.arr, items.start, items.Length); + } +#nullable restore } public readonly ref struct SpanLike From 658fb39d1efaa6006223dc48c9863e6f88f8af4f Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 19 Jul 2024 09:58:19 +0200 Subject: [PATCH 2/8] Simplify assert --- .../CSharp/Portable/Binder/Binder_Conversions.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index e61ebf6cc4dbb..c04520ddf7a6e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -623,14 +623,7 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) var sameElementTypes = sourceElementType.Equals(destinationElementType, TypeCompareKind.AllIgnoreOptions); - if (source.OriginalDefinition.IsReadOnlySpan()) - { - Debug.Assert(!sameElementTypes); - } - else - { - Debug.Assert(source.OriginalDefinition.IsSpan()); - } + Debug.Assert(!source.OriginalDefinition.IsReadOnlySpan() || !sameElementTypes); return !sameElementTypes; } From e6c53869100a2ef2ac7018ce3c7f14f1f8e2d442 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 19 Jul 2024 10:37:28 +0200 Subject: [PATCH 3/8] Test more Span UDCs --- .../CSharp/Test/Emit3/FirstClassSpanTests.cs | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index be10c6f61e3b8..92741bf9670b4 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -2097,6 +2097,47 @@ class C Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.ReadOnlySpan").WithLocation(3, 49)); } + [Fact] + public void Conversion_Span_Array_Implicit_UserDefined() + { + var source = """ + using System; + class C + { + string[] M(Span arg) => arg; + } + namespace System + { + public readonly ref struct Span + { + public static implicit operator T[](Span span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "string[] System.Span.op_Implicit(System.Span)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,37): error CS0029: Cannot implicitly convert type 'System.Span' to 'string[]' + // string[] M(Span arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "string[]").WithLocation(4, 37) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + [Theory, MemberData(nameof(LangVersions))] public void Conversion_Array_ReadOnlySpan_Explicit_UnrelatedElementType(LanguageVersion langVersion) { @@ -2274,6 +2315,47 @@ struct S; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(5, 64)); } + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_UserDefined() + { + var source = """ + using System; + class C + { + ReadOnlySpan M(ReadOnlySpan arg) => arg; + } + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static implicit operator ReadOnlySpan(ReadOnlySpan span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,57): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 57) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + [Theory, CombinatorialData] public void Conversion_ReadOnlySpan_Span_Implicit( [CombinatorialLangVersions] LanguageVersion langVersion, @@ -2310,6 +2392,132 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, $"(Span<{type}>)arg").WithArguments("System.ReadOnlySpan", $"System.Span<{type}>").WithLocation(4, 49)); } + [Fact] + public void Conversion_ReadOnlySpan_Span_Implicit_UserDefined() + { + var source = """ + using System; + class C + { + Span M(ReadOnlySpan arg) => arg; + } + namespace System + { + public readonly ref struct Span; + public readonly ref struct ReadOnlySpan + { + public static implicit operator Span(ReadOnlySpan span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.Span System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,49): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined() + { + var source = """ + using System; + class C + { + Span M(ReadOnlySpan arg) => (Span)arg; + } + namespace System + { + public readonly ref struct Span; + public readonly ref struct ReadOnlySpan + { + public static explicit operator Span(ReadOnlySpan span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.Span System.ReadOnlySpan.op_Explicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,49): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined_Covariant() + { + var source = """ + using System; + class C + { + Span M(ReadOnlySpan arg) => (Span)arg; + } + namespace System + { + public readonly ref struct Span; + public readonly ref struct ReadOnlySpan + { + public static explicit operator Span(ReadOnlySpan span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.Span System.ReadOnlySpan.op_Explicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,49): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' + // Span M(ReadOnlySpan arg) => (Span)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + [Theory, MemberData(nameof(LangVersions))] public void Conversion_string_ReadOnlySpan_Implicit_UnrelatedElementType(LanguageVersion langVersion) { @@ -2363,6 +2571,77 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("string", "System.Span").WithLocation(4, 33)); } + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_Span_Implicit_UserDefined(LanguageVersion langVersion) + { + var source = """ + using System; + class C + { + Span M(string arg) => arg; + } + namespace System + { + public readonly ref struct Span + { + public static implicit operator Span(string s) => default; + } + } + """; + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.Span System.Span.op_Implicit(string)" + IL_0006: ret + } + """); + } + + [Fact] + public void Conversion_ReadOnlySpan_string_Implicit_UserDefined() + { + var source = """ + using System; + class C + { + string M(ReadOnlySpan arg) => arg; + } + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static implicit operator string(ReadOnlySpan span) => default; + } + } + """; + + CompileAndVerify(source, parseOptions: TestOptions.Regular13) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "string System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + + var expectedDiagnostics = new[] + { + // (4,41): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'string' + // string M(ReadOnlySpan arg) => arg; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 41) + }; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); + CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + } + [Fact] public void Conversion_Array_Span_Implicit_NullableAnalysis() { From d9e4b719f83b29c813db1e873475a3870f654400 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 24 Jul 2024 15:26:42 +0200 Subject: [PATCH 4/8] Only ignore UDCs exactly matching compiler-defined conversions --- .../Semantics/Conversions/ConversionsBase.cs | 35 +-- .../Portable/FlowAnalysis/NullableWalker.cs | 4 +- .../CSharp/Test/Emit3/FirstClassSpanTests.cs | 207 +++++++----------- 3 files changed, 81 insertions(+), 165 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index bca7036b54ccc..28aec8cc3f17c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -4037,37 +4037,12 @@ private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? target) { + // SPEC: User-defined conversions are not considered when converting between types + // for which an implicit or an explicit span conversion exists. + var discarded = CompoundUseSiteInfo.Discarded; return source is not null && target is not null && - IsFeatureFirstClassSpanEnabled && - (ignoreUserDefinedSpanConversionsInAnyDirection(source, target) || - ignoreUserDefinedSpanConversionsInOneDirection(source, target) || - ignoreUserDefinedSpanConversionsInOneDirection(target, source)); - - static bool ignoreUserDefinedSpanConversionsInOneDirection(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.IsSpan() || b.OriginalDefinition.IsReadOnlySpan())) - { - return true; - } - - // SPEC: - `string` and `System.ReadOnlySpan` - if (a.IsStringType() && b.IsReadOnlySpanChar()) - { - return true; - } - - return false; - } - - static bool ignoreUserDefinedSpanConversionsInAnyDirection(TypeSymbol a, TypeSymbol b) - { - // SPEC: - any combination of `System.Span`/`System.ReadOnlySpan` - return (a.OriginalDefinition.IsSpan() || a.OriginalDefinition.IsReadOnlySpan()) && - (b.OriginalDefinition.IsSpan() || b.OriginalDefinition.IsReadOnlySpan()); - } + (HasImplicitSpanConversion(source, target, ref discarded) || + HasExplicitSpanConversion(source, target, ref discarded)); } } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3be4f5f03c861..92f245f3dc41b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -8904,8 +8904,8 @@ private TypeWithState VisitConversion( { var previousKind = conversion.Kind; conversion = GenerateConversion(_conversions, conversionOperand, operandType.Type, targetType, fromExplicitCast, extensionMethodThisArgument, isChecked: conversionOpt?.Checked ?? false); - Debug.Assert(!conversion.Exists || conversion.Kind == previousKind); - canConvertNestedNullability = conversion.Exists; + // We do not want user-defined conversions to relax nullability, so we consider only span conversions. + canConvertNestedNullability = conversion.Exists && conversion.IsSpan; } break; diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 92741bf9670b4..238c0e2c6f4e5 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -2097,8 +2097,8 @@ class C Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("int[]", "System.ReadOnlySpan").WithLocation(3, 49)); } - [Fact] - public void Conversion_Span_Array_Implicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_Array_Implicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -2114,8 +2114,7 @@ public readonly ref struct Span } } """; - - CompileAndVerify(source, parseOptions: TestOptions.Regular13) + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)) .VerifyDiagnostics() .VerifyIL("C.M", """ { @@ -2126,16 +2125,6 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,37): error CS0029: Cannot implicitly convert type 'System.Span' to 'string[]' - // string[] M(Span arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span", "string[]").WithLocation(4, 37) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Theory, MemberData(nameof(LangVersions))] @@ -2315,8 +2304,8 @@ struct S; Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.ReadOnlySpan>", "System.ReadOnlySpan>").WithLocation(5, 64)); } - [Fact] - public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_UserDefined_01(LanguageVersion langVersion) { var source = """ using System; @@ -2332,6 +2321,37 @@ public readonly ref struct ReadOnlySpan } } """; + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify) + .VerifyDiagnostics() + .VerifyIL("C.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + + [Fact] + public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_UserDefined_02() + { + var source = """ + using System; + class C + { + ReadOnlySpan M(ReadOnlySpan arg) => arg; + } + namespace System + { + public readonly ref struct ReadOnlySpan + { + public static implicit operator ReadOnlySpan(ReadOnlySpan span) => default; + public static ReadOnlySpan CastUp(ReadOnlySpan s) where TDerived : class, T => throw null; + } + } + """; CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) .VerifyDiagnostics() @@ -2340,20 +2360,25 @@ public readonly ref struct ReadOnlySpan // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.1 - IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" + IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(System.ReadOnlySpan)" IL_0006: ret } """); - var expectedDiagnostics = new[] - { - // (4,57): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' - // ReadOnlySpan M(ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(4, 57) - }; + var expectedIl = """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "System.ReadOnlySpan System.ReadOnlySpan.CastUp(System.ReadOnlySpan)" + IL_0006: ret + } + """; - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); + CompileAndVerify(source, parseOptions: TestOptions.RegularNext, verify: Verification.FailsILVerify) + .VerifyDiagnostics().VerifyIL("C.M", expectedIl); + CompileAndVerify(source, verify: Verification.FailsILVerify) + .VerifyDiagnostics().VerifyIL("C.M", expectedIl); } [Theory, CombinatorialData] @@ -2392,8 +2417,8 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, $"(Span<{type}>)arg").WithArguments("System.ReadOnlySpan", $"System.Span<{type}>").WithLocation(4, 49)); } - [Fact] - public void Conversion_ReadOnlySpan_Span_Implicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_Span_Implicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -2410,8 +2435,7 @@ public readonly ref struct ReadOnlySpan } } """; - - CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify) .VerifyDiagnostics() .VerifyIL("C.M", """ { @@ -2422,20 +2446,10 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,49): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span' - // Span M(ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } - [Fact] - public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -2452,8 +2466,7 @@ public readonly ref struct ReadOnlySpan } } """; - - CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify) .VerifyDiagnostics() .VerifyIL("C.M", """ { @@ -2464,20 +2477,10 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,49): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' - // Span M(ReadOnlySpan arg) => (Span)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } - [Fact] - public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined_Covariant() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_Span_Explicit_UserDefined_Covariant(LanguageVersion langVersion) { var source = """ using System; @@ -2494,8 +2497,7 @@ public readonly ref struct ReadOnlySpan } } """; - - CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify) + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify) .VerifyDiagnostics() .VerifyIL("C.M", """ { @@ -2506,16 +2508,6 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,49): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' - // Span M(ReadOnlySpan arg) => (Span)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 49) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Theory, MemberData(nameof(LangVersions))] @@ -2601,8 +2593,8 @@ .maxstack 1 """); } - [Fact] - public void Conversion_ReadOnlySpan_string_Implicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_ReadOnlySpan_string_Implicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -2618,8 +2610,7 @@ public readonly ref struct ReadOnlySpan } } """; - - CompileAndVerify(source, parseOptions: TestOptions.Regular13) + CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)) .VerifyDiagnostics() .VerifyIL("C.M", """ { @@ -2630,16 +2621,6 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,41): error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan' to 'string' - // string M(ReadOnlySpan arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 41) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Fact] @@ -3655,8 +3636,8 @@ class C Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string)arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 43)); } - [Fact] - public void Conversion_Array_Span_Opposite_Explicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_Opposite_Explicit_UserDefined(LanguageVersion langVersion) { var source = """ class C @@ -3672,7 +3653,7 @@ readonly ref struct Span } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -3683,20 +3664,10 @@ .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] - public void Conversion_Span_ReadOnlySpan_Opposite_Explicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Span_ReadOnlySpan_Opposite_Explicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -3714,7 +3685,7 @@ readonly ref struct ReadOnlySpan } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13, verify: Verification.FailsILVerify); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion), verify: Verification.FailsILVerify); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -3725,20 +3696,10 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,44): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.Span' - // Span M(ReadOnlySpan arg) => (Span)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(Span)arg").WithArguments("System.ReadOnlySpan", "System.Span").WithLocation(4, 44) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } - [Fact] - public void Conversion_string_ReadOnlySpan_Opposite_Explicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_string_ReadOnlySpan_Opposite_Explicit_UserDefined(LanguageVersion langVersion) { var source = """ using System; @@ -3755,7 +3716,7 @@ readonly ref struct ReadOnlySpan } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -3766,16 +3727,6 @@ .maxstack 1 IL_0006: ret } """); - - var expectedDiagnostics = new[] - { - // (4,42): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'string' - // string M(ReadOnlySpan arg) => (string)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string)arg").WithArguments("System.ReadOnlySpan", "string").WithLocation(4, 42) - }; - - CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); - CreateCompilation(source).VerifyDiagnostics(expectedDiagnostics); } [Theory, MemberData(nameof(LangVersions))] @@ -6275,8 +6226,8 @@ static void E3(this string[] arg) { } Diagnostic(ErrorCode.ERR_NoExplicitConv, "(string[])arg").WithArguments("System.ReadOnlySpan", "string[]").WithLocation(6, 57)); } - [Fact] - public void Conversion_Array_Span_ExtensionMethodReceiver_Opposite_Explicit_UserDefined() + [Theory, MemberData(nameof(LangVersions))] + public void Conversion_Array_Span_ExtensionMethodReceiver_Opposite_Explicit_UserDefined(LanguageVersion langVersion) { var source = """ static class C @@ -6293,7 +6244,7 @@ readonly ref struct Span } } """; - var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular13); + var verifier = CompileAndVerify(source, parseOptions: TestOptions.Regular.WithLanguageVersion(langVersion)); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { @@ -6305,16 +6256,6 @@ .maxstack 1 IL_000b: ret } """); - - 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] From 62fea5f2fda6a8cd1b8f866e193f6403b05ff05e Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 1 Aug 2024 10:44:32 +0200 Subject: [PATCH 5/8] Simplify code --- .../Portable/Binder/Binder_Conversions.cs | 43 +++++++++---------- .../Semantics/Conversions/ConversionsBase.cs | 12 +++--- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index c04520ddf7a6e..f17a79313865b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -488,7 +488,7 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn else if (conversion.IsSpan) { Debug.Assert(source.Type is not null); - Debug.Assert(destination.OriginalDefinition.IsSpan() || destination.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(destination.IsSpan() || destination.IsReadOnlySpan()); CheckFeatureAvailability(syntax, MessageID.IDS_FeatureFirstClassSpan, diagnostics); @@ -507,9 +507,9 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn } // ReadOnlySpan Span.op_Implicit(Span) - if (source.Type.OriginalDefinition.IsSpan()) + if (source.Type.IsSpan()) { - Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(destination.IsReadOnlySpan()); reportUseSiteOrMissing( TryFindImplicitOperatorFromSpan(source.Type.OriginalDefinition, destination.OriginalDefinition), source.Type.OriginalDefinition, @@ -519,19 +519,19 @@ void checkConstraintLanguageVersionAndRuntimeSupportForConversion(SyntaxNode syn } // ReadOnlySpan ReadOnlySpan.CastUp(ReadOnlySpan) - if (source.Type.OriginalDefinition.IsSpan() || source.Type.OriginalDefinition.IsReadOnlySpan()) + if (source.Type.IsSpan() || source.Type.IsReadOnlySpan()) { - Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(destination.IsReadOnlySpan()); if (NeedsSpanCastUp(source.Type, destination)) { // If converting Span -> ROS -> ROS, // the source of the CastUp is the return type of the op_Implicit (i.e., the ROS) // which has the same original definition as the destination ROS. - TypeSymbol sourceForCastUp = source.Type.OriginalDefinition.IsSpan() + TypeSymbol sourceForCastUp = source.Type.IsSpan() ? destination.OriginalDefinition : source.Type.OriginalDefinition; - MethodSymbol? castUpMethod = TryFindCastUpMethod(sourceForCastUp.OriginalDefinition, destination.OriginalDefinition); + MethodSymbol? castUpMethod = TryFindCastUpMethod(sourceForCastUp, destination.OriginalDefinition); reportUseSiteOrMissing( castUpMethod, destination.OriginalDefinition, @@ -581,8 +581,8 @@ static void reportUseSiteOrMissing(MethodSymbol? method, object containingType, Debug.Assert(type.IsSpan() || type.IsReadOnlySpan()); Debug.Assert(type.IsDefinition); - return TryFindImplicitOperator(type, 0, static (_, parameterType) => - parameterType is ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol }); + return TryFindImplicitOperator(type, 0, static (_, method) => + method.Parameters[0].Type is ArrayTypeSymbol { IsSZArray: true, ElementType: TypeParameterSymbol }); } // ReadOnlySpan Span.op_Implicit(Span) @@ -592,30 +592,27 @@ static void reportUseSiteOrMissing(MethodSymbol? method, object containingType, Debug.Assert(spanType.IsDefinition && readonlySpanType.IsDefinition); return TryFindImplicitOperator(spanType, readonlySpanType, - parameterPredicate: static (_, parameterType) => parameterType.IsSpan(), - returnTypePredicate: static (readonlySpanType, returnType) => readonlySpanType.Equals(returnType.OriginalDefinition, TypeCompareKind.ConsiderEverything)); + static (readonlySpanType, method) => method.Parameters[0].Type.IsSpan() && + readonlySpanType.Equals(method.ReturnType.OriginalDefinition, TypeCompareKind.ConsiderEverything)); } private static MethodSymbol? TryFindImplicitOperator(TypeSymbol type, TArg arg, - Func parameterPredicate, - Func? returnTypePredicate = null) + Func predicate) { - return TryFindSingleMember(type, WellKnownMemberNames.ImplicitConversionName, (parameterPredicate, returnTypePredicate, arg), + return TryFindSingleMethod(type, WellKnownMemberNames.ImplicitConversionName, (predicate, arg), static (arg, method) => method is { ParameterCount: 1, Arity: 0, IsStatic: true, DeclaredAccessibility: Accessibility.Public, - Parameters: [{ Type: { } parameterType }], - } && arg.parameterPredicate(arg.arg, parameterType) && - arg.returnTypePredicate?.Invoke(arg.arg, method.ReturnType) != false); + } && arg.predicate(arg.arg, method)); } internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) { - Debug.Assert(source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan()); - Debug.Assert(destination.OriginalDefinition.IsReadOnlySpan()); + Debug.Assert(source.IsSpan() || source.IsReadOnlySpan()); + Debug.Assert(destination.IsReadOnlySpan()); Debug.Assert(!source.IsDefinition && !destination.IsDefinition); var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type; @@ -623,7 +620,7 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) var sameElementTypes = sourceElementType.Equals(destinationElementType, TypeCompareKind.AllIgnoreOptions); - Debug.Assert(!source.OriginalDefinition.IsReadOnlySpan() || !sameElementTypes); + Debug.Assert(!source.IsReadOnlySpan() || !sameElementTypes); return !sameElementTypes; } @@ -634,7 +631,7 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) Debug.Assert(source.IsReadOnlySpan() && destination.IsReadOnlySpan()); Debug.Assert(source.IsDefinition && destination.IsDefinition); - return TryFindSingleMember(destination, WellKnownMemberNames.CastUpMethodName, (source, destination), + return TryFindSingleMethod(destination, WellKnownMemberNames.CastUpMethodName, (source, destination), static (arg, method) => method is { ParameterCount: 1, @@ -666,7 +663,7 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) MethodSymbol? result = null; foreach (var memoryExtensionsType in compilation.GetTypesByMetadataName(WellKnownMemberNames.MemoryExtensionsTypeFullName)) { - if (TryFindSingleMember(memoryExtensionsType.GetSymbol(), WellKnownMemberNames.AsSpanMethodName, 0, + if (TryFindSingleMethod(memoryExtensionsType.GetSymbol(), WellKnownMemberNames.AsSpanMethodName, 0, static (_, method) => method is { ParameterCount: 1, @@ -690,7 +687,7 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) return result; } - private static MethodSymbol? TryFindSingleMember(TypeSymbol type, string name, TArg arg, Func predicate) + private static MethodSymbol? TryFindSingleMethod(TypeSymbol type, string name, TArg arg, Func predicate) { var members = type.GetMembers(name); MethodSymbol? result = null; diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 28aec8cc3f17c..bec89470a71cb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -3962,14 +3962,14 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType }) { // SPEC: ...to `System.Span`. - if (destination.OriginalDefinition.IsSpan()) + if (destination.IsSpan()) { var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; return hasIdentityConversion(elementType, spanElementType); } // SPEC: ...to `System.ReadOnlySpan`, provided that `Ei` is covariance-convertible to `Ui`. - if (destination.OriginalDefinition.IsReadOnlySpan()) + if (destination.IsReadOnlySpan()) { var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; return hasCovariantConversion(elementType, spanElementType, ref useSiteInfo); @@ -3977,9 +3977,9 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio } // SPEC: From `System.Span` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible to `Ui`. // SPEC: From `System.ReadOnlySpan` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible to `Ui`. - else if (source.OriginalDefinition.IsSpan() || source.OriginalDefinition.IsReadOnlySpan()) + else if (source.IsSpan() || source.IsReadOnlySpan()) { - if (destination.OriginalDefinition.IsReadOnlySpan()) + if (destination.IsReadOnlySpan()) { var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; @@ -3989,7 +3989,7 @@ private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio // SPEC: From `string` to `System.ReadOnlySpan`. else if (source.IsStringType()) { - if (destination.OriginalDefinition.IsReadOnlySpan()) + if (destination.IsReadOnlySpan()) { var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; return spanElementType.SpecialType is SpecialType.System_Char; @@ -4025,7 +4025,7 @@ private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destinatio // 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.IsSpan() || destination.OriginalDefinition.IsReadOnlySpan())) + (destination.IsSpan() || destination.IsReadOnlySpan())) { var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0]; return HasIdentityOrReferenceConversion(elementType.Type, spanElementType.Type, ref useSiteInfo) && From 56466b14add32c4bb4e7b813e977dcfe2e72b5de Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 1 Aug 2024 10:55:07 +0200 Subject: [PATCH 6/8] Remove a redundant test --- .../CSharp/Test/Emit3/FirstClassSpanTests.cs | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 238c0e2c6f4e5..491d1cdd9889b 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -3139,35 +3139,6 @@ interface I { } Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("I[]", targetType).WithLocation(5, 52)); } - [Fact] - public void Conversion_Span_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability_Covariant() - { - var source = """ - #nullable enable - using System; - class C - { - ReadOnlySpan> M(Span> arg) => arg; - } - interface I { } - """; - - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.Regular13).VerifyDiagnostics( - // (5,56): error CS0029: Cannot implicitly convert type 'System.Span>' to 'System.ReadOnlySpan>' - // ReadOnlySpan> M(Span> arg) => arg; - Diagnostic(ErrorCode.ERR_NoImplicitConv, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(5, 56)); - - var expectedDiagnostics = new[] - { - // (5,56): warning CS8619: Nullability of reference types in value of type 'Span>' doesn't match target type 'ReadOnlySpan>'. - // ReadOnlySpan> M(Span> arg) => arg; - Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "arg").WithArguments("System.Span>", "System.ReadOnlySpan>").WithLocation(5, 56) - }; - - CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); - CreateCompilationWithSpanAndMemoryExtensions(source, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); - } - [Fact] public void Conversion_ReadOnlySpan_ReadOnlySpan_Implicit_NullableAnalysis_NestedNullability_Covariant() { From 5efca6d15c1bc0405a563d8dea56314b9db4ae45 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 1 Aug 2024 11:03:43 +0200 Subject: [PATCH 7/8] Extend tests --- .../CSharp/Test/Emit3/FirstClassSpanTests.cs | 129 ++++++++++++++++-- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 491d1cdd9889b..38c614b990a0a 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -516,7 +516,7 @@ .maxstack 1 } [Theory, CombinatorialData] - public void Conversion_String_ReadOnlySpan_Implicit(bool cast) + public void Conversion_string_ReadOnlySpan_Implicit(bool cast) { var source = $$""" using System; @@ -818,7 +818,8 @@ public readonly ref struct Span [InlineData("ReadOnlySpan AsSpan(string s)", false)] [InlineData("ReadOnlySpan AsSpan(string s)", false)] [InlineData("ReadOnlySpan AsSpan(object o)", false)] - public void Conversion_Array_Span_Implicit_DifferentHelper(string signature, bool works) + [InlineData("Span AsSpan(string s)", false)] + public void Conversion_string_ReadOnlySpan_Implicit_DifferentHelper(string signature, bool works) { var source = $$""" using System; @@ -828,6 +829,7 @@ public void Conversion_Array_Span_Implicit_DifferentHelper(string signature, boo namespace System { public readonly ref struct ReadOnlySpan { } + public readonly ref struct Span { } public static class MemoryExtensions { public static {{signature}} => default; @@ -849,6 +851,96 @@ public static class MemoryExtensions } } + [Theory, CombinatorialData] + public void Conversion_string_ReadOnlySpan_Implicit_HelperAccessibility_01( + [CombinatorialValues("public", "internal")] string classModifier, + [CombinatorialValues("public", "internal")] string methodModifier) + { + var source = $$""" + using System; + ReadOnlySpan s = source(); + static string source() => ""; + + namespace System + { + public readonly ref struct ReadOnlySpan { } + {{classModifier}} static class MemoryExtensions + { + {{methodModifier}} static ReadOnlySpan AsSpan(string s) => default; + } + } + """; + var comp = CreateCompilation(source); + if (methodModifier == "internal") + { + comp.VerifyDiagnostics( + // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 24)); + } + else + { + comp.VerifyDiagnostics(); + } + } + + [Fact] + public void Conversion_string_ReadOnlySpan_Implicit_HelperAccessibility_02() + { + var source1 = """ + namespace System; + public readonly ref struct ReadOnlySpan; + internal static class MemoryExtensions + { + public static ReadOnlySpan AsSpan(string s) => default; + } + """; + var comp1 = CreateCompilation(source1).VerifyDiagnostics().EmitToImageReference(); + + var source2 = """ + using System; + ReadOnlySpan s1 = source(); + ReadOnlySpan s2 = MemoryExtensions.AsSpan(source()); + static string source() => ""; + """; + CreateCompilation(source2, [comp1]).VerifyDiagnostics( + // (3,25): error CS0122: 'MemoryExtensions' is inaccessible due to its protection level + // ReadOnlySpan s2 = MemoryExtensions.AsSpan(source()); + Diagnostic(ErrorCode.ERR_BadAccess, "MemoryExtensions").WithArguments("System.MemoryExtensions").WithLocation(3, 25)); + } + + [Theory, CombinatorialData] + public void Conversion_string_ReadOnlySpan_Implicit_RedefinedHelper( + [CombinatorialValues("public", "internal")] string classModifier, + [CombinatorialValues("public", "internal")] string methodModifier) + { + var source = $$""" + using System; + ReadOnlySpan s = source(); + static string source() => ""; + + namespace System + { + {{classModifier}} static class MemoryExtensions + { + {{methodModifier}} static ReadOnlySpan AsSpan(string s) => default; + } + } + """; + var comp = CreateCompilationWithSpanAndMemoryExtensions(source); + if (methodModifier == "public") + { + comp.VerifyDiagnostics( + // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // ReadOnlySpan s = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 24)); + } + else + { + comp.VerifyDiagnostics(); + } + } + [Fact] public void Conversion_Array_Span_Implicit_ExplicitOperator() { @@ -2699,6 +2791,7 @@ class C ReadOnlySpan M6(string?[] arg) => (ReadOnlySpan)arg; ReadOnlySpan M7(object?[] arg) => (ReadOnlySpan)arg; + ReadOnlySpan M8(string?[] arg) => (ReadOnlySpan)arg; } """; @@ -2714,7 +2807,10 @@ class C 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)); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "string[]").WithLocation(12, 47), + // (13,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'object[]'. + // ReadOnlySpan M8(string?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[]", "object[]").WithLocation(13, 47)); var expectedDiagnostics = new[] { @@ -2729,7 +2825,10 @@ class C 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) + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("object?[]", "System.ReadOnlySpan").WithLocation(12, 47), + // (13,47): warning CS8619: Nullability of reference types in value of type 'string?[]' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M8(string?[] arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("string?[]", "System.ReadOnlySpan").WithLocation(13, 47) }; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expectedDiagnostics); @@ -2752,6 +2851,7 @@ class C ReadOnlySpan M6(Span arg) => (ReadOnlySpan)arg; ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; + ReadOnlySpan M8(Span arg) => (ReadOnlySpan)arg; } """; @@ -2770,7 +2870,10 @@ class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 51), // (12,51): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' // ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51)); + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51), + // (13,51): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' + // ReadOnlySpan M8(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(13, 51)); var expectedDiagnostics = new[] { @@ -2785,7 +2888,10 @@ class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(11, 51), // (12,51): error CS0030: Cannot convert type 'System.Span' to 'System.ReadOnlySpan' // ReadOnlySpan M7(Span arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51) + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(12, 51), + // (13,51): warning CS8619: Nullability of reference types in value of type 'Span' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M8(Span arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.Span", "System.ReadOnlySpan").WithLocation(13, 51) }; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); @@ -2808,6 +2914,7 @@ class C ReadOnlySpan M6(ReadOnlySpan arg) => (ReadOnlySpan)arg; ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; + ReadOnlySpan M8(ReadOnlySpan arg) => (ReadOnlySpan)arg; } """; @@ -2826,7 +2933,10 @@ class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 59), // (12,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' // ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59)); + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59), + // (13,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' + // ReadOnlySpan M8(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(13, 59)); var expectedDiagnostics = new[] { @@ -2841,7 +2951,10 @@ class C Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 59), // (12,59): error CS0030: Cannot convert type 'System.ReadOnlySpan' to 'System.ReadOnlySpan' // ReadOnlySpan M7(ReadOnlySpan arg) => (ReadOnlySpan)arg; - Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59) + Diagnostic(ErrorCode.ERR_NoExplicitConv, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(12, 59), + // (13,59): warning CS8619: Nullability of reference types in value of type 'ReadOnlySpan' doesn't match target type 'ReadOnlySpan'. + // ReadOnlySpan M8(ReadOnlySpan arg) => (ReadOnlySpan)arg; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "(ReadOnlySpan)arg").WithArguments("System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(13, 59) }; CreateCompilationWithSpanAndMemoryExtensions(source, parseOptions: TestOptions.RegularNext, targetFramework: TargetFramework.Net90).VerifyDiagnostics(expectedDiagnostics); From be2c29d7881a0c93f75bf14a29543b0d5a2bf400 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 1 Aug 2024 12:10:19 +0200 Subject: [PATCH 8/8] Require public MemoryExtensions --- src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs | 3 ++- src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index f17a79313865b..677e658dbb601 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -663,7 +663,8 @@ internal static bool NeedsSpanCastUp(TypeSymbol source, TypeSymbol destination) MethodSymbol? result = null; foreach (var memoryExtensionsType in compilation.GetTypesByMetadataName(WellKnownMemberNames.MemoryExtensionsTypeFullName)) { - if (TryFindSingleMethod(memoryExtensionsType.GetSymbol(), WellKnownMemberNames.AsSpanMethodName, 0, + if (memoryExtensionsType.DeclaredAccessibility == Accessibility.Public && + TryFindSingleMethod(memoryExtensionsType.GetSymbol(), WellKnownMemberNames.AsSpanMethodName, 0, static (_, method) => method is { ParameterCount: 1, diff --git a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs index 38c614b990a0a..14050ec846eae 100644 --- a/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FirstClassSpanTests.cs @@ -871,7 +871,7 @@ public readonly ref struct ReadOnlySpan { } } """; var comp = CreateCompilation(source); - if (methodModifier == "internal") + if (classModifier == "internal" || methodModifier == "internal") { comp.VerifyDiagnostics( // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' @@ -904,6 +904,9 @@ internal static class MemoryExtensions static string source() => ""; """; CreateCompilation(source2, [comp1]).VerifyDiagnostics( + // (2,25): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan' + // ReadOnlySpan s1 = source(); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "source()").WithArguments("System.MemoryExtensions", "AsSpan").WithLocation(2, 25), // (3,25): error CS0122: 'MemoryExtensions' is inaccessible due to its protection level // ReadOnlySpan s2 = MemoryExtensions.AsSpan(source()); Diagnostic(ErrorCode.ERR_BadAccess, "MemoryExtensions").WithArguments("System.MemoryExtensions").WithLocation(3, 25)); @@ -928,7 +931,7 @@ namespace System } """; var comp = CreateCompilationWithSpanAndMemoryExtensions(source); - if (methodModifier == "public") + if (classModifier == "public" && methodModifier == "public") { comp.VerifyDiagnostics( // (2,24): error CS0656: Missing compiler required member 'System.MemoryExtensions.AsSpan'