diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index eb2e9e9ca5037..16e36acac21c8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -796,7 +797,7 @@ private void ReportAssignmentOperatorError(AssignmentExpressionSyntax node, Bind } } - private static void ReportBinaryOperatorError(ExpressionSyntax node, BindingDiagnosticBag diagnostics, SyntaxToken operatorToken, BoundExpression left, BoundExpression right, LookupResultKind resultKind) + private void ReportBinaryOperatorError(ExpressionSyntax node, BindingDiagnosticBag diagnostics, SyntaxToken operatorToken, BoundExpression left, BoundExpression right, LookupResultKind resultKind) { bool isEquality = operatorToken.Kind() == SyntaxKind.EqualsEqualsToken || operatorToken.Kind() == SyntaxKind.ExclamationEqualsToken; switch (left.Kind, right.Kind) @@ -825,11 +826,33 @@ private static void ReportBinaryOperatorError(ExpressionSyntax node, BindingDiag return; } - ErrorCode errorCode = resultKind == LookupResultKind.Ambiguous ? - ErrorCode.ERR_AmbigBinaryOps : // Operator '{0}' is ambiguous on operands of type '{1}' and '{2}' - ErrorCode.ERR_BadBinaryOps; // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' +#nullable enable + ErrorCode errorCode; + + switch (resultKind) + { + case LookupResultKind.Ambiguous: + errorCode = ErrorCode.ERR_AmbigBinaryOps; // Operator '{0}' is ambiguous on operands of type '{1}' and '{2}' + break; + + case LookupResultKind.OverloadResolutionFailure when operatorToken.Kind() is SyntaxKind.PlusToken && isReadOnlySpanOfByte(left.Type) && isReadOnlySpanOfByte(right.Type): + errorCode = ErrorCode.ERR_BadBinaryReadOnlySpanConcatenation; // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + break; + + default: + errorCode = ErrorCode.ERR_BadBinaryOps; // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' + break; + } Error(diagnostics, errorCode, node, operatorToken.Text, left.Display, right.Display); + + bool isReadOnlySpanOfByte(TypeSymbol? type) + { + return type is NamedTypeSymbol namedType && Compilation.IsReadOnlySpanType(namedType) && + namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type.SpecialType is SpecialType.System_Byte; + + } +#nullable disable } private BoundExpression BindConditionalLogicalOperator(BinaryExpressionSyntax node, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs index 7503d975686a7..fec419b443fd2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/BinaryOperatorOverloadResolution.cs @@ -730,6 +730,13 @@ private void GetAllBuiltInOperators(BinaryOperatorKind kind, bool isChecked, Bou // We similarly limit pointer operator candidates considered. GetPointerOperators(kind, left, right, operators); + + if (kind.Operator() is BinaryOperatorKind.Addition && + isUTF8ByteRepresentation(left) && + isUTF8ByteRepresentation(right)) + { + this.Compilation.builtInOperators.GetUTF8ConcatenationBuiltInOperator(left.Type, operators); + } } CandidateOperators(isChecked, operators, left, right, results, ref useSiteInfo); @@ -743,6 +750,11 @@ static bool useOnlyReferenceEquality(Conversions conversions, BoundExpression le ((object)left.Type == null || (!left.Type.IsDelegateType() && left.Type.SpecialType != SpecialType.System_String && left.Type.SpecialType != SpecialType.System_Delegate)) && ((object)right.Type == null || (!right.Type.IsDelegateType() && right.Type.SpecialType != SpecialType.System_String && right.Type.SpecialType != SpecialType.System_Delegate)); } + + static bool isUTF8ByteRepresentation(BoundExpression value) + { + return value is BoundUTF8String or BoundBinaryOperator { OperatorKind: BinaryOperatorKind.UTF8Addition }; + } } private void GetReferenceEquality(BinaryOperatorKind kind, ArrayBuilder operators) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorKind.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorKind.cs index 16cbaf8f475b4..a4ed1f86f299b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorKind.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorKind.cs @@ -51,6 +51,7 @@ internal enum UnaryOperatorKind _NullableNull = 0x00000027, // reserved for binary op UserDefined = 0x00000028, Dynamic = 0x00000029, + _UTF8 = 0x0000002A, // reserved for binary op OpMask = 0x0000FF00, PostfixIncrement = 0x00001000, @@ -324,6 +325,7 @@ internal enum BinaryOperatorKind NullableNull = UnaryOperatorKind._NullableNull, UserDefined = UnaryOperatorKind.UserDefined, Dynamic = UnaryOperatorKind.Dynamic, + UTF8 = UnaryOperatorKind._UTF8, OpMask = 0x0000FF00, Multiplication = 0x00001000, @@ -453,6 +455,7 @@ internal enum BinaryOperatorKind ObjectAndStringConcatenation = ObjectAndString | Addition, DelegateCombination = Delegate | Addition, DynamicAddition = Dynamic | Addition, + UTF8Addition = UTF8 | Addition, IntSubtraction = Int | Subtraction, UIntSubtraction = UInt | Subtraction, diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 739071047a4fd..66334f01f4a92 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7133,4 +7133,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ One of the parameters of an equality, or inequality operator declared in interface '{0}' must be a type parameter on '{0}' constrained to '{0}' + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + diff --git a/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs b/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs index 813305b283018..b87c4a2be2291 100644 --- a/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs +++ b/src/Compilers/CSharp/Portable/Compilation/BuiltInOperators.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; @@ -25,6 +27,7 @@ internal class BuiltInOperators //actual lazily-constructed caches of built-in operators. private ImmutableArray[] _builtInUnaryOperators; private ImmutableArray[][] _builtInOperators; + private StrongBox _builtInUTF8Concatenation; internal BuiltInOperators(CSharpCompilation compilation) { @@ -684,6 +687,21 @@ internal void GetSimpleBuiltInOperators(BinaryOperatorKind kind, ArrayBuilder operators) + { + Debug.Assert(_compilation.IsReadOnlySpanType(readonlySpanOfByte)); + Debug.Assert(((NamedTypeSymbol)readonlySpanOfByte).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type.SpecialType is SpecialType.System_Byte); + + if (_builtInUTF8Concatenation is null) + { + Interlocked.CompareExchange(ref _builtInUTF8Concatenation, + new StrongBox( + new BinaryOperatorSignature(BinaryOperatorKind.UTF8Addition, readonlySpanOfByte, readonlySpanOfByte, readonlySpanOfByte)), null); + } + + operators.Add(_builtInUTF8Concatenation.Value); + } + internal BinaryOperatorSignature GetSignature(BinaryOperatorKind kind) { var left = LeftType(kind); diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 25d43049cd901..52f28a8d3bc24 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2091,6 +2091,7 @@ internal enum ErrorCode ERR_ImplicitImplementationOfInaccessibleInterfaceMember = 9044, ERR_ScriptsAndSubmissionsCannotHaveRequiredMembers = 9045, ERR_BadAbstractEqualityOperatorSignature = 9046, + ERR_BadBinaryReadOnlySpanConcatenation = 9047, #endregion diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs index 8b87aa34896e9..474cd7ee676c6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_BinaryOperator.cs @@ -117,6 +117,12 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO return LowerPartsToString(data, parts, node.Syntax, node.Type); } + if (node.OperatorKind is BinaryOperatorKind.UTF8Addition) + { + Debug.Assert(applyParentUnaryOperator is null); + return VisitUTF8Addition(node); + } + // In machine-generated code we frequently end up with binary operator trees that are deep on the left, // such as a + b + c + d ... // To avoid blowing the call stack, we make an explicit stack of the binary operators to the left, @@ -127,7 +133,7 @@ public BoundExpression VisitBinaryOperator(BoundBinaryOperator node, BoundUnaryO for (BoundBinaryOperator? current = node; current != null && current.ConstantValue == null; current = current.Left as BoundBinaryOperator) { // The regular visit mechanism will handle this. - if (current.InterpolatedStringHandlerData is not null) + if (current.InterpolatedStringHandlerData is not null || current.OperatorKind is BinaryOperatorKind.UTF8Addition) { Debug.Assert(stack.Count >= 1); break; @@ -513,6 +519,9 @@ private BoundExpression MakeBinaryOperator( goto default; + case BinaryOperatorKind.UTF8Addition: + throw ExceptionUtilities.UnexpectedValue(operatorKind); + default: break; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index 2b9e9f89da43c..7e1109298877a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -75,16 +76,25 @@ public override BoundNode VisitConversion(BoundConversion node) public override BoundNode VisitUTF8String(BoundUTF8String node) { - Debug.Assert(node.Type.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)); + return MakeUTF8Span(node, GetUTF8ByteRepresentation(node)); + } + + private BoundExpression MakeUTF8Span(BoundExpression node, IReadOnlyList? bytes) + { + Debug.Assert(node.Type is not null); + Debug.Assert(_compilation.IsReadOnlySpanType(node.Type)); var byteType = ((NamedTypeSymbol)node.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type; Debug.Assert(byteType.SpecialType == SpecialType.System_Byte); var save_Syntax = _factory.Syntax; _factory.Syntax = node.Syntax; - int length; - BoundExpression utf8Bytes = createUTF8ByteRepresentation(node.Syntax, node.Value, ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, TypeWithAnnotations.Create(byteType)), out length); - BoundNode result; + int length = 0; + BoundExpression result; + var byteArray = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, TypeWithAnnotations.Create(byteType)); + BoundExpression utf8Bytes = bytes is null ? + BadExpression(node.Syntax, byteArray, ImmutableArray.Empty) : + MakeUnderlyingArrayForUTF8Span(node.Syntax, byteArray, bytes, out length); if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, out MethodSymbol ctor)) { @@ -98,48 +108,97 @@ public override BoundNode VisitUTF8String(BoundUTF8String node) _factory.Syntax = save_Syntax; return result; + } + + private byte[]? GetUTF8ByteRepresentation(BoundUTF8String node) + { + var utf8 = new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - BoundExpression createUTF8ByteRepresentation(SyntaxNode syntax, string value, ArrayTypeSymbol byteArray, out int length) + try { - Debug.Assert(byteArray.IsSZArray); - Debug.Assert(byteArray.ElementType.SpecialType == SpecialType.System_Byte); + return utf8.GetBytes(node.Value); + } + catch (Exception ex) + { + _diagnostics.Add( + ErrorCode.ERR_CannotBeConvertedToUTF8, + node.Syntax.Location, + ex.Message); - var utf8 = new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - byte[] bytes; + return null; + } + } - try - { - bytes = utf8.GetBytes(value); - } - catch (Exception ex) - { - _diagnostics.Add( - ErrorCode.ERR_CannotBeConvertedToUTF8, - syntax.Location, - ex.Message); + private BoundArrayCreation MakeUnderlyingArrayForUTF8Span(SyntaxNode syntax, ArrayTypeSymbol byteArray, IReadOnlyList bytes, out int length) + { + Debug.Assert(byteArray.IsSZArray); + Debug.Assert(byteArray.ElementType.SpecialType == SpecialType.System_Byte); - length = 0; - return BadExpression(syntax, byteArray, ImmutableArray.Empty); - } + var builder = ArrayBuilder.GetInstance(bytes.Count + 1); + foreach (byte b in bytes) + { + builder.Add(_factory.Literal(b)); + } + + length = builder.Count; - var builder = ArrayBuilder.GetInstance(bytes.Length); - foreach (byte b in bytes) + // Zero terminate memory + builder.Add(_factory.Literal((byte)0)); + + var utf8Bytes = new BoundArrayCreation( + syntax, + ImmutableArray.Create(_factory.Literal(builder.Count)), + new BoundArrayInitialization(syntax, isInferred: false, builder.ToImmutableAndFree()), + byteArray); + return utf8Bytes; + } + + private BoundExpression VisitUTF8Addition(BoundBinaryOperator node) + { + Debug.Assert(node.OperatorKind is BinaryOperatorKind.UTF8Addition); + + var bytesBuilder = ArrayBuilder.GetInstance(); + bool haveRepresentationError = false; + var stack = ArrayBuilder.GetInstance(); + + stack.Add(node); + + while (stack.Count != 0) + { + var current = stack.Pop(); + + switch (current) { - builder.Add(_factory.Literal(b)); - } + case BoundUTF8String literal: + byte[]? bytes = GetUTF8ByteRepresentation(literal); - length = builder.Count; + if (bytes is null) + { + haveRepresentationError = true; + } + else if (!haveRepresentationError) + { + bytesBuilder.AddRange(bytes); + } + break; - // Zero terminate memory - builder.Add(_factory.Literal((byte)0)); + case BoundBinaryOperator utf8Addition: + Debug.Assert(utf8Addition.OperatorKind is BinaryOperatorKind.UTF8Addition); + stack.Push(utf8Addition.Right); + stack.Push(utf8Addition.Left); + break; - var utf8Bytes = new BoundArrayCreation( - syntax, - ImmutableArray.Create(_factory.Literal(builder.Count)), - new BoundArrayInitialization(syntax, isInferred: false, builder.ToImmutableAndFree()), - byteArray); - return utf8Bytes; + default: + throw ExceptionUtilities.UnexpectedValue(current); + } } + + stack.Free(); + + BoundExpression result = MakeUTF8Span(node, haveRepresentationError ? null : bytesBuilder); + + bytesBuilder.Free(); + return result; } private static bool IsFloatingPointExpressionOfUnknownPrecision(BoundExpression rewrittenNode) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 35995064a9043..7aadc2c4848fa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -152,6 +152,11 @@ Parametr unárního operátoru musí být nadřazeného typu nebo jeho parametrem obecného typu, který se na něj omezuje. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute jde použít jenom pro parametry s výchozími hodnotami. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 87372f5bce5c8..1c25686851fd2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -152,6 +152,11 @@ Ein Parameter eines unären Operators muss der enthaltende Typ sein oder sein zugehöriger Typparameter, der darauf beschränkt ist. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values Das CallerArgumentExpressionAttribute kann nur auf Parameter mit Standardwerten angewendet werden. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 1901137a8effa..8aa5fb02d05b6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -152,6 +152,11 @@ El parámetro de un operador unario debe ser el tipo contenedor o su parámetro de tipo restringido a él. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute solo se puede aplicar a parámetros con valores predeterminados diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index c5f7c28457835..ea018499be15b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -152,6 +152,11 @@ Le paramètre d’un opérateur unaire doit être le type conteneur ou son paramètre de type lui être contraint. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values Le CallerArgumentExpressionAttribute peut seulement être appliqué aux paramètres avec des valeurs par défaut diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 64ada2a7cbe21..1658f707208ea 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -152,6 +152,11 @@ I parametri di un operatore unario deve essere il tipo che lo contiene o il relativo parametro di tipo vincolato ad esso. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute può essere applicato solo a parametri con valori predefiniti diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index c19f53c3d127a..b3f077be5a7cc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -152,6 +152,11 @@ 単項演算子のパラメーターは、それを含む型であるか、それに制約された型パラメーターである必要があります。 + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute は、既定値を含むパラメーターにのみ適用できます diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 094f5e0de8658..fd9bbb7c2a3ee 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -152,6 +152,11 @@ 단항 연산자의 매개 변수는 포함하는 유형이거나 이에 제한되는 유형 매개 변수여야 합니다. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute는 기본값이 있는 매개 변수에만 적용할 수 있습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 9e6d0933c32b1..dd6add2589847 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -152,6 +152,11 @@ Parametr operatora jednoargumentowego musi być typem zawierającym lub jest on parametrem typu ograniczonym do niego. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values Atrybut CallerArgumentExpressionAttribute można zastosować tylko do parametrów z wartościami domyślnymi diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 59630b99e44bb..17d4b1705467d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -152,6 +152,11 @@ O parâmetro de um operador unário deve ser do tipo recipiente ou seu parâmetro de tipo restrito a ele. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values O CallerArgumentExpressionAttribute só pode ser aplicado a parâmetros com valores padrão diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 72532dcd50a40..8c9241a3eb2de 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -152,6 +152,11 @@ Параметр унарного оператора должен быть содержащим типом, или параметр его типа должен ограничиваться только этим параметром. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values Класс CallerArgumentExpressionAttribute можно применять только к параметрам со значениями по умолчанию. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 1ba42ffc2c2fd..35a19fddd8339 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -152,6 +152,11 @@ Birli işlecin parametresi, içeren tür veya tür parametresi ile kısıtlanmış olmalıdır. + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute yalnızca varsayılan değeri olan parametrelere uygulanabilir diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 2b91bbf4144be..bb25a0a45dd23 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -152,6 +152,11 @@ 一元运算符的参数必须是包含类型或被其约束的类型参数。 + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute 只能应用于具有默认值的参数 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 3e36aa1cc9825..f16b585b26224 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -152,6 +152,11 @@ 一元運算子的其中一個參數必須是包含類型,或其型別參數受其限制。 + + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF8 byte representations + + The CallerArgumentExpressionAttribute may only be applied to parameters with default values CallerArgumentExpressionAttribute 只能套用至具有預設值的參數 diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUTF8StringOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUTF8StringOperation.cs index 0b2351ccf5952..73d1196f28931 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUTF8StringOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IUTF8StringOperation.cs @@ -70,6 +70,74 @@ void M(System.ReadOnlySpan b) Block[B2] - Exit Predecessors: [B1] Statements (0) +"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, targetFramework: Roslyn.Test.Utilities.TargetFramework.NetCoreApp); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void UTF8String_02() + { + string source = @" +class Program +{ + static System.ReadOnlySpan Test() + { + /**/return ""Ab""u8 + ""c""u8;/**/ + } +} +"; + string expectedOperationTree = @" +IReturnOperation (OperationKind.Return, Type: null) (Syntax: 'return ""Ab""u8 + ""c""u8;') + ReturnedValue: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.ReadOnlySpan) (Syntax: '""Ab""u8 + ""c""u8') + Left: + IUTF8StringOperation (Ab) (OperationKind.UTF8String, Type: System.ReadOnlySpan) (Syntax: '""Ab""u8') + Right: + IUTF8StringOperation (c) (OperationKind.UTF8String, Type: System.ReadOnlySpan) (Syntax: '""c""u8') +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, targetFramework: Roslyn.Test.Utilities.TargetFramework.NetCoreApp); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact] + public void UTF8StringFlow_02() + { + string source = @" +class C +{ + void M(System.ReadOnlySpan b) + /**/{ + b = ""AB""u8 + ""C""u8; + }/**/ +} +"; + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'b = ""AB""u8 + ""C""u8;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.ReadOnlySpan) (Syntax: 'b = ""AB""u8 + ""C""u8') + Left: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.ReadOnlySpan) (Syntax: 'b') + Right: + IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.ReadOnlySpan) (Syntax: '""AB""u8 + ""C""u8') + Left: + IUTF8StringOperation (AB) (OperationKind.UTF8String, Type: System.ReadOnlySpan) (Syntax: '""AB""u8') + Right: + IUTF8StringOperation (C) (OperationKind.UTF8String, Type: System.ReadOnlySpan) (Syntax: '""C""u8') + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0) "; VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, targetFramework: Roslyn.Test.Utilities.TargetFramework.NetCoreApp); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs index bdce70e47243c..0e4b509912366 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/Utf8StringsLiteralsTests.cs @@ -187,6 +187,31 @@ static void Main() ); } + [ConditionalFact(typeof(CoreClrOnly))] + public void InvalidContent_03() + { + var source = @" +class C +{ + static void Main() + { + _ = ""\uD83D\uDE00""u8; + _ = ""\uD83D""u8 + ""\uDE00""u8; + } +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics( + // (7,13): error CS9026: The input string cannot be converted into the equivalent UTF-8 byte representation. Unable to translate Unicode character \\uD83D at index 0 to specified code page. + // _ = "\uD83D"u8 + "\uDE00"u8; + Diagnostic(ErrorCode.ERR_CannotBeConvertedToUTF8, @"""\uD83D""u8").WithArguments(@"Unable to translate Unicode character \\uD83D at index 0 to specified code page.").WithLocation(7, 13), + // (7,26): error CS9026: The input string cannot be converted into the equivalent UTF-8 byte representation. Unable to translate Unicode character \\uDE00 at index 0 to specified code page. + // _ = "\uD83D"u8 + "\uDE00"u8; + Diagnostic(ErrorCode.ERR_CannotBeConvertedToUTF8, @"""\uDE00""u8").WithArguments(@"Unable to translate Unicode character \\uDE00 at index 0 to specified code page.").WithLocation(7, 26) + ); + } + [ConditionalFact(typeof(CoreClrOnly))] public void NoBehaviorChangeForConversionFromNullLiteral_01() { @@ -1196,6 +1221,36 @@ static void Main() ); } + [Fact] + public void ExpressionTree_04() + { + var source = @" +using System; +using System.Linq.Expressions; +class C +{ + static void Main() + { + Expression> x = () => (""h""u8 + ""ello""u8).ToArray(); + System.Console.WriteLine(x); + } +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + + comp.VerifyDiagnostics( + // (8,45): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. + // Expression> x = () => ("h"u8 + "ello"u8).ToArray(); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, @"""h""u8 + ""ello""u8").WithArguments("ReadOnlySpan").WithLocation(8, 45), + // (8,45): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. + // Expression> x = () => ("h"u8 + "ello"u8).ToArray(); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, @"""h""u8").WithArguments("ReadOnlySpan").WithLocation(8, 45), + // (8,53): error CS8640: Expression tree cannot contain value of ref struct or restricted type 'ReadOnlySpan'. + // Expression> x = () => ("h"u8 + "ello"u8).ToArray(); + Diagnostic(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, @"""ello""u8").WithArguments("ReadOnlySpan").WithLocation(8, 53) + ); + } + [ConditionalFact(typeof(CoreClrOnly))] public void OverloadResolution_01() { @@ -3049,14 +3104,17 @@ ReadOnlySpan Test() Assert.True(model.GetConversion(node).IsIdentity); } - [Fact] - public void NullTerminate_01() + [Theory] + [InlineData(@"""cat""u8")] + [InlineData(@"""c""u8 + ""at""u8")] + [InlineData(@"""c""u8 + ""a""u8 + ""t""u8")] + public void NullTerminate_01(string expression) { var source = @" using System; class C { - static ReadOnlySpan Test3() => ""cat""u8; + static ReadOnlySpan Test3() => " + expression + @"; } "; var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseDll); @@ -3080,14 +3138,17 @@ 63 61 74 00 "); } - [Fact] - public void NullTerminate_02() + [Theory] + [InlineData(@"""""u8")] + [InlineData(@"""""u8 + """"u8")] + [InlineData(@"""""u8 + """"u8 + """"u8")] + public void NullTerminate_02(string expression) { var source = @" using System; class C { - static ReadOnlySpan Test3() => """"u8; + static ReadOnlySpan Test3() => " + expression + @"; } "; var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.ReleaseDll); @@ -3483,7 +3544,7 @@ static void Main() ", verify: Verification.Fails).VerifyDiagnostics(); } - [ConditionalFact(typeof(CoreClrOnly))] + [Fact] public void PassAround_02() { var source = @" @@ -3509,5 +3570,478 @@ static ref readonly ReadOnlySpan Test2() Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, @"""cat""u8").WithLocation(7, 26) ); } + + [ConditionalFact(typeof(CoreClrOnly))] + public void UserDefinedConcatenation_01() + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = new C() + ReadOnlySpan.Empty; + } + + public static C operator +(C x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).VerifyDiagnostics(); + } + + [ConditionalFact(typeof(CoreClrOnly))] + public void UserDefinedConcatenation_02() + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = new C() + ""a""u8; + } + + public static C operator +(C x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).VerifyDiagnostics(); + } + + [Fact] + public void UserDefinedConcatenation_03() + { + var source = @" +using System; +public class C +{ + static void Main() + { + _ = new C() + ReadOnlySpan.Empty; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} + +namespace System +{ + public readonly ref struct ReadOnlySpan + { + private readonly T[] arr; + + public ref readonly T this[int i] => ref arr[i]; + public override int GetHashCode() => 2; + public int Length { get; } + + public ReadOnlySpan(T[] arr) + { + this.arr = arr; + this.Length = arr.Length; + } + + public static ReadOnlySpan Empty => default; + + public static C operator +(C x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + } +} +"; + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).Diagnostics.Where(d => d.Code is not (int)ErrorCode.WRN_SameFullNameThisAggAgg).Verify(); + } + + [Fact] + public void UserDefinedConcatenation_04() + { + var source = @" +using System; +public class C +{ + static void Main() + { + _ = new C() + ""a""u8; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} + +namespace System +{ + public readonly ref struct ReadOnlySpan + { + private readonly T[] arr; + + public ref readonly T this[int i] => ref arr[i]; + public override int GetHashCode() => 2; + public int Length { get; } + + public ReadOnlySpan(T[] arr, int start, int length) + { + this.arr = arr; + this.Length = arr.Length; + } + + public static ReadOnlySpan Empty => default; + + public static C operator +(C x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + } +} +"; + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).Diagnostics.Where(d => d.Code is not (int)ErrorCode.WRN_SameFullNameThisAggAgg).Verify(); + } + + [Fact] + public void UserDefinedConcatenation_05() + { + var source = @" +using System; +public class C +{ + static void Main() + { + _ = ReadOnlySpan.Empty + ReadOnlySpan.Empty; + } +} + +namespace System +{ + public readonly ref struct ReadOnlySpan + { + private readonly T[] arr; + + public ref readonly T this[int i] => ref arr[i]; + public override int GetHashCode() => 2; + public int Length { get; } + + public ReadOnlySpan(T[] arr) + { + this.arr = arr; + this.Length = arr.Length; + } + + public static ReadOnlySpan Empty => default; + + public static ReadOnlySpan operator +(ReadOnlySpan x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + } +} +"; + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).Diagnostics.Where(d => d.Code is not (int)ErrorCode.WRN_SameFullNameThisAggAgg).Verify(); + } + + [Fact] + public void UserDefinedConcatenation_06() + { + var source = @" +public class C +{ + static void Main() + { + _ = ""a""u8 + ""b""u8; + } +} + +namespace System +{ + public readonly ref struct ReadOnlySpan + { + private readonly T[] arr; + + public ref readonly T this[int i] => ref arr[i]; + public override int GetHashCode() => 2; + public int Length { get; } + + public ReadOnlySpan(T[] arr, int start, int length) + { + this.arr = arr; + this.Length = arr.Length; + } + + public static ReadOnlySpan Empty => default; + + public static ReadOnlySpan operator +(ReadOnlySpan x, ReadOnlySpan y) + { + System.Console.WriteLine(""called""); + return x; + } + } +} +"; + var comp = CreateCompilation(source, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @"called", verify: Verification.Fails).Diagnostics.Where(d => d.Code is not (int)ErrorCode.WRN_SameFullNameThisAggAgg).Verify(); + } + + [ConditionalTheory(typeof(CoreClrOnly))] + [InlineData(@"""12""u8 + ""34""u8")] + [InlineData(@"""12""u8 + ""3""u8 + ""4""u8")] + [InlineData(@"""12""u8 + (""3""u8 + ""4""u8)")] + [InlineData(@"""1""u8 + ""2""u8 + ""3""u8 + ""4""u8")] + [InlineData(@"""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)")] + [InlineData(@"""1""u8 + (""2""u8 + ""3""u8 + ""4""u8)")] + [InlineData(@"""1""u8 + (""2""u8 + (""3""u8 + ""4""u8))")] + [InlineData(@"""1""u8 + checked(""2""u8 + unchecked(""3""u8 + ""4""u8))")] + public void Concatenation_01(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + System.Console.WriteLine(); + Helpers.Print(Test3()); + } + + static ReadOnlySpan Test3() => " + expression + @"; +} +"; + var comp = CreateCompilation(source + HelpersSource, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + + var verifier = CompileAndVerify(comp, expectedOutput: @" +{ 0x31 0x32 0x33 0x34 } +", verify: Verification.Fails).VerifyDiagnostics(); + + verifier.VerifyIL("C.Test3()", @" +{ + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldsflda "".__StaticArrayInitTypeSize=5 .94E7DD2D7CA2487FF2B04D7CBB490139BB9A2B5CE798348F02CA0A29ABD4EFF1"" + IL_0005: ldc.i4.4 + IL_0006: newobj ""System.ReadOnlySpan..ctor(void*, int)"" + IL_000b: ret +} +"); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + + foreach (var node in tree.GetCompilationUnitRoot().DescendantNodes().OfType().Where(b => b.IsKind(SyntaxKind.AddExpression))) + { + var method = (IMethodSymbol)model.GetSymbolInfo(node).Symbol; + Assert.Equal("System.ReadOnlySpan System.ReadOnlySpan.op_Addition(System.ReadOnlySpan left, System.ReadOnlySpan right)", method.ToTestDisplayString()); + Assert.True(method.IsImplicitlyDeclared); + Assert.Equal(MethodKind.BuiltinOperator, method.MethodKind); + } + } + + [ConditionalTheory(typeof(CoreClrOnly))] + [InlineData(@"(""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)) + new C()")] + [InlineData(@"(""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)) + new C() + new C()")] + [InlineData(@"new C() + (""1""u8 + ""2""u8 + (""3""u8 + ""4""u8))")] + [InlineData(@"new C() + (""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)) + new C()")] + [InlineData(@"new C() + new C() + (""1""u8 + ""2""u8 + (""3""u8 + ""4""u8))")] + [InlineData(@"new C() + ((""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)) + new C())")] + [InlineData(@"new C() + (new C() + (""1""u8 + ""2""u8 + (""3""u8 + ""4""u8)))")] + public void Concatenation_02(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + System.Console.WriteLine(); + _ = " + expression + @"; + } + + public static C operator +(ReadOnlySpan x, C y) + { + Helpers.Print(x); + return y; + } + + public static C operator +(C x, ReadOnlySpan y) + { + Helpers.Print(y); + return x; + } + + public static C operator +(C x, C y) + { + return x; + } +} +"; + var comp = CreateCompilation(source + HelpersSource, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + + CompileAndVerify(comp, expectedOutput: @" +{ 0x31 0x32 0x33 0x34 } +", verify: Verification.Fails).VerifyDiagnostics(); + } + + [Theory] + [InlineData(@"ReadOnlySpan.Empty + ReadOnlySpan.Empty")] + [InlineData(@"""a""u8 + ReadOnlySpan.Empty")] + [InlineData(@"ReadOnlySpan.Empty + ""b""u8")] + public void Concatenation_03(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = " + expression + @"; + } +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (7,13): error CS9047: Operator '+' cannot be applied to operands of type 'ReadOnlySpan' and 'ReadOnlySpan' that are not UTF8 byte representations + // _ = ReadOnlySpan.Empty + ReadOnlySpan.Empty; + Diagnostic(ErrorCode.ERR_BadBinaryReadOnlySpanConcatenation, expression).WithArguments("+", "System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(7, 13) + ); + } + + [Theory] + [InlineData(@"ReadOnlySpan.Empty")] + [InlineData(@"""b""u8")] + public void Concatenation_04(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + var x = ReadOnlySpan.Empty; + var y = ""a""u8; + + x += " + expression + @"; + y += " + expression + @"; + ""c""u8 += " + expression + @"; + x++; + y++; + ""d""u8++; + } +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (10,9): error CS0019: Operator '+=' cannot be applied to operands of type 'ReadOnlySpan' and 'ReadOnlySpan' + // x += "b"u8; + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"x += " + expression).WithArguments("+=", "System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(10, 9), + // (11,9): error CS0019: Operator '+=' cannot be applied to operands of type 'ReadOnlySpan' and 'ReadOnlySpan' + // y += "b"u8; + Diagnostic(ErrorCode.ERR_BadBinaryOps, @"y += " + expression).WithArguments("+=", "System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(11, 9), + // (12,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer + // "c"u8 += "b"u8; + Diagnostic(ErrorCode.ERR_AssgLvalueExpected, @"""c""u8").WithLocation(12, 9), + // (13,9): error CS0023: Operator '++' cannot be applied to operand of type 'ReadOnlySpan' + // x++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "x++").WithArguments("++", "System.ReadOnlySpan").WithLocation(13, 9), + // (14,9): error CS0023: Operator '++' cannot be applied to operand of type 'ReadOnlySpan' + // y++; + Diagnostic(ErrorCode.ERR_BadUnaryOp, "y++").WithArguments("++", "System.ReadOnlySpan").WithLocation(14, 9), + // (15,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer + // "d"u8++; + Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, @"""d""u8").WithLocation(15, 9) + ); + } + + [Theory] + [InlineData(@"ReadOnlySpan.Empty - ReadOnlySpan.Empty")] + [InlineData(@"""a""u8 - ReadOnlySpan.Empty")] + [InlineData(@"ReadOnlySpan.Empty - ""b""u8")] + public void Subtraction_01(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = " + expression + @"; + } +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (7,13): error CS0019: Operator '-' cannot be applied to operands of type 'ReadOnlySpan' and 'ReadOnlySpan' + // _ = ReadOnlySpan.Empty - ReadOnlySpan.Empty; + Diagnostic(ErrorCode.ERR_BadBinaryOps, expression).WithArguments("-", "System.ReadOnlySpan", "System.ReadOnlySpan").WithLocation(7, 13) + ); + } + + [Theory] + [InlineData(@"new C() + ReadOnlySpan.Empty")] + [InlineData(@"new C() + ""b""u8")] + public void Concatenation_05(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = " + expression + @"; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (7,13): error CS0019: Operator '+' cannot be applied to operands of type 'C' and 'ReadOnlySpan' + // _ = new C() + ReadOnlySpan.Empty; + Diagnostic(ErrorCode.ERR_BadBinaryOps, expression).WithArguments("+", "C", "System.ReadOnlySpan").WithLocation(7, 13) + ); + } + + [Theory] + [InlineData(@"ReadOnlySpan.Empty + new C()")] + [InlineData(@"""b""u8 + new C()")] + public void Concatenation_06(string expression) + { + var source = @" +using System; +class C +{ + static void Main() + { + _ = " + expression + @"; + } + + public static implicit operator ReadOnlySpan(C x) => ReadOnlySpan.Empty; +} +"; + var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (7,13): error CS0019: Operator '+' cannot be applied to operands of type 'ReadOnlySpan' and 'C' + // _ = "b"u8 + new C(); + Diagnostic(ErrorCode.ERR_BadBinaryOps, expression).WithArguments("+", "System.ReadOnlySpan", "C").WithLocation(7, 13) + ); + } } }