diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - post VS2017.md b/docs/compilers/CSharp/Compiler Breaking Changes - post VS2017.md index 7209c967961209b1723a5b03a539a7523b4cfb0e..a02442c5b08cebe64e63ce49ac630df4fcacd692 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - post VS2017.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - post VS2017.md @@ -23,5 +23,3 @@ Consider the case where the type of `a` is `System.Func` and you write `va - https://github.com/dotnet/roslyn/issues/17963 In C# 7.0 and before C# 7.1, the compiler used to consider tuple element name differences and dynamic-ness differences as significant when using the "as" operator with a nullable tuple type. For instance, `(1, 1) as (int, int x)?` and `(1, new object()) as (int, dynamic)?` would always be considered `null`. The compiler now produces the correct value, instead of `null`, and no "always null" warning. - https://github.com/dotnet/roslyn/issues/20208 In C# 7.0 and before C# 7.2, the compiler considered a local declared with a `var` type and a tuple literal value to be used. So it would not report a warning if that local was not used. The compiler now produces a diagnostic. For example, `var unused = (1, 2);`. - -- https://github.com/dotnet/roslyn/pull/21006 In this PR, stackalloc expressions are target typed, instead of always being of type T* pointer. This affects the semantic model, as getting type info about such nodes will return null types. Users can still access the implicit conversion to know the new target type. diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 3d3afbb82e99c58df8d2544413dc50ce13a762b9..561149874dc12139707ae30ea2a87ba329a7c58b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1887,7 +1887,7 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin case BoundKind.Local: return ((BoundLocal)expr).LocalSymbol.ValEscapeScope; - case BoundKind.StackAllocArrayCreation: + case BoundKind.ConvertedStackAllocExpression: return Binder.TopLevelScope; case BoundKind.ConditionalOperator: @@ -2054,7 +2054,7 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint } return true; - case BoundKind.StackAllocArrayCreation: + case BoundKind.ConvertedStackAllocExpression: if (escapeTo < Binder.TopLevelScope) { Error(diagnostics, ErrorCode.ERR_EscapeStackAlloc, node, expr.Type); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 84b3abdf4e88b481799b80b5c5d2e1fabac344c3..19a111a39d3450b7bce175f6557396f2228491e3 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; 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; @@ -93,6 +95,11 @@ internal partial class Binder return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast, destination, diagnostics); } + if (conversion.IsStackAlloc) + { + return CreateStackAllocConversion(syntax, source, conversion, isCast, destination, diagnostics); + } + if (conversion.IsTupleLiteralConversion || (conversion.IsNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion)) { @@ -312,6 +319,32 @@ private BoundExpression CreateMethodGroupConversion(SyntaxNode syntax, BoundExpr return new BoundConversion(syntax, group, conversion, @checked: false, explicitCastInCode: isCast, constantValueOpt: ConstantValue.NotAvailable, type: destination, hasErrors: hasErrors) { WasCompilerGenerated = source.WasCompilerGenerated }; } + private BoundExpression CreateStackAllocConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics) + { + Debug.Assert(conversion.IsStackAlloc); + + var boundStackAlloc = (BoundStackAllocArrayCreation)source; + var elementType = boundStackAlloc.ElementType; + TypeSymbol stackAllocType; + + switch (conversion.Kind) + { + case ConversionKind.StackAllocToPointerType: + stackAllocType = new PointerTypeSymbol(elementType); + break; + case ConversionKind.StackAllocToSpanType: + stackAllocType = Compilation.GetWellKnownType(WellKnownType.System_Span_T).Construct(elementType); + break; + default: + throw ExceptionUtilities.UnexpectedValue(conversion.Kind); + } + + var convertedNode = new BoundConvertedStackAllocExpression(syntax, elementType, boundStackAlloc.Count, conversion.Kind, stackAllocType, boundStackAlloc.HasErrors); + + var underlyingConversion = conversion.UnderlyingConversions.Single(); + return CreateConversion(syntax, convertedNode, underlyingConversion, isCast, destination, diagnostics); + } + private BoundExpression CreateTupleLiteralConversion(SyntaxNode syntax, BoundTupleLiteral sourceTuple, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics) { // We have a successful tuple conversion; rather than producing a separate conversion node diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index dbff97935ec657557ea8081917541eeedf79db21..dd54ee6e1d5a50366b6717873b303c2e7dff72c9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -3102,7 +3102,7 @@ private void BindArrayInitializerExpressions(InitializerExpressionSyntax initial } } - return new BoundStackAllocArrayCreation(node, default(ConversionKind), elementType, count, null, hasErrors || typeHasErrors); + return new BoundStackAllocArrayCreation(node, elementType, count, type: null, hasErrors: hasErrors || typeHasErrors); } private static int? GetIntegerConstantForArraySize(BoundExpression expression) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index e9a38f41f037b84c56894b2bd979b501ad24764b..01b74baba18afe8667da86a35653c1cbcbf0a493 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -2193,21 +2193,10 @@ internal bool IsNonMoveableVariable(BoundExpression expr, out Symbol accessedLoc } case BoundKind.PointerIndirectionOperator: //Covers ->, since the receiver will be one of these. case BoundKind.PointerElementAccess: - case BoundKind.StackAllocArrayCreation: + case BoundKind.ConvertedStackAllocExpression: { return true; } - case BoundKind.Conversion: - { - switch (expr.GetConversion().Kind) - { - case ConversionKind.StackAllocToPointerType: - case ConversionKind.StackAllocToSpanType: - return true; - default: - return false; - } - } case BoundKind.PropertyAccess: // Never a variable. case BoundKind.IndexerAccess: // Never a variable. default: diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 944c8c8b1205f25aab88592ea8b4a62c2c3720dd..c16043abf69c1c4f8a592fbb9df597af25ec4855 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -709,12 +709,11 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic BindValueKind valueKind; ExpressionSyntax value; IsInitializerRefKindValid(initializer, initializer, refKind, diagnostics, out valueKind, out value); // The return value isn't important here; we just want the diagnostics and the BindValueKind - return BindInferredVariableInitializer(diagnostics, value, valueKind, errorSyntax); + return BindInferredVariableInitializer(diagnostics, value, valueKind, refKind, errorSyntax); } // The location where the error is reported might not be the initializer. - protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnostics, ExpressionSyntax initializer, BindValueKind valueKind, - CSharpSyntaxNode errorSyntax) + protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnostics, ExpressionSyntax initializer, BindValueKind valueKind, RefKind refKind, CSharpSyntaxNode errorSyntax) { if (initializer == null) { @@ -736,6 +735,12 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic BoundExpression expression = BindValue(initializer, diagnostics, valueKind); + if (expression is BoundStackAllocArrayCreation boundStackAlloc) + { + var type = new PointerTypeSymbol(boundStackAlloc.ElementType); + expression = GenerateConversionForAssignment(type, boundStackAlloc, diagnostics, refKind: refKind); + } + // Certain expressions (null literals, method groups and anonymous functions) have no type of // their own and therefore cannot be the initializer of an implicitly typed local. if (!expression.HasAnyErrors && !expression.HasExpressionType()) @@ -860,7 +865,7 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic { aliasOpt = null; - initializerOpt = BindInferredVariableInitializer(diagnostics, value, valueKind, declarator); + initializerOpt = BindInferredVariableInitializer(diagnostics, value, valueKind, localSymbol.RefKind, declarator); // If we got a good result then swap the inferred type for the "var" if ((object)initializerOpt?.Type != null) @@ -883,11 +888,6 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic } } } - else if (initializerOpt is BoundStackAllocArrayCreation boundStackAlloc) - { - declTypeOpt = new PointerTypeSymbol(boundStackAlloc.ElementType); - initializerOpt = GenerateConversionForAssignment(declTypeOpt, boundStackAlloc, diagnostics, false, localSymbol.RefKind); - } else { declTypeOpt = CreateErrorType("var"); @@ -906,7 +906,7 @@ private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, Diagnostic initializerOpt = BindPossibleArrayInitializer(value, declTypeOpt, valueKind, diagnostics); if (kind != LocalDeclarationKind.FixedVariable) { - // If this is for a fixed statement, we'll do our own conversion since there are some special cases. + // If this is for a fixed statement, we'll do our own conversion since there are some special cases. initializerOpt = GenerateConversionForAssignment(declTypeOpt, initializerOpt, localDiagnostics, refKind: localSymbol.RefKind); } } @@ -1055,14 +1055,13 @@ private bool IsValidFixedVariableInitializer(TypeSymbol declType, SourceLocalSym if (ReferenceEquals(initializerType, null)) { - // Dev10 just reports the assignment conversion error (which must occur, unless the initializer is a null literal). initializerOpt = GenerateConversionForAssignment(declType, initializerOpt, diagnostics); if (!initializerOpt.HasAnyErrors) { - Debug.Assert(initializerOpt is BoundConversion conversion && ( - conversion.Operand.IsLiteralNull() || - conversion.Operand.Kind == BoundKind.DefaultExpression || - conversion.Operand.Kind == BoundKind.StackAllocArrayCreation), + // Dev10 just reports the assignment conversion error, which must occur, except for these cases: + Debug.Assert( + initializerOpt is BoundConvertedStackAllocExpression || + initializerOpt is BoundConversion conversion && (conversion.Operand.IsLiteralNull() || conversion.Operand.Kind == BoundKind.DefaultExpression), "All other typeless expressions should have conversion errors"); // CONSIDER: this is a very confusing error message, but it's what Dev10 reports. diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index fa8f3789b32a6d52ac6dfb278dac3256f8d665fb..ca8250515df510985658c9aab9592236f78deae2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -457,6 +457,18 @@ public bool IsIdentity } } + /// + /// Returns true if the conversion is a stackalloc conversion. + /// PROTOTYPE(span) review public API change + /// + public bool IsStackAlloc + { + get + { + return Kind == ConversionKind.StackAllocToPointerType || Kind == ConversionKind.StackAllocToSpanType; + } + } + /// /// Returns true if the conversion is an implicit numeric conversion or explicit numeric conversion. /// diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs index e253e63c21a9085709b0151cc45659db8b301530..3963778dd91e53a93b9f7a53bed8cee7d19f479a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs @@ -44,12 +44,6 @@ public static bool IsDefaultValue(this BoundExpression node) public static bool HasExpressionType(this BoundExpression node) { - if (node.Kind == BoundKind.StackAllocArrayCreation) - { - // stackalloc expressions have no 'Type', but an 'ElementType' that is target-typed based on context. - return true; - } - // null literal, method group, and anonymous function expressions have no type. return (object)node.Type != null; } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index c1cf585a1f4ec438ce068cd44e4940f6b0380d71..9b14703b9a96a0de5606c81f1dc6677ae2736cd4 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1431,11 +1431,19 @@ - + + + + + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index fb72b11227243d10d8330a241afdbb3810fa9229..4c5c019c8aff5606a4860eac6b41ca854851d47c 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -1964,6 +1964,23 @@ public override void Accept(OperationVisitor visitor) } } + internal partial class BoundConvertedStackAllocExpression + { + protected override OperationKind ExpressionKind => OperationKind.None; + + protected override ImmutableArray Children => ImmutableArray.Create(this.Count); + + public override void Accept(OperationVisitor visitor) + { + visitor.VisitNoneOperation(this); + } + + public override TResult Accept(OperationVisitor visitor, TArgument argument) + { + return visitor.VisitNoneOperation(this, argument); + } + } + internal partial class BoundDynamicObjectCreationExpression { protected override OperationKind ExpressionKind => OperationKind.None; diff --git a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs index 01debb3f2cd63836cabcb72942b33a31677a9d36..3bb5642f6a3e732df55bb05ec8fa78f3e3f29a49 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs @@ -149,7 +149,7 @@ internal partial class BoundStackAllocArrayCreation { public override object Display { - get { return string.Format(MessageID.IDS_StackAllocExpression.Localize().ToString(), ElementType); } + get { return string.Format(MessageID.IDS_StackAllocExpression.Localize().ToString(), ElementType, Count.Syntax); } } } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index f77f5152946cade354218f23a11e3a83b1c3786c..30f17371c53d6d32a8cdca81efc7a753b5045c93 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -10955,7 +10955,7 @@ internal static string ERR_RefReturnParameter } /// - /// Looks up a localized string similar to stackalloc expression of type '{0}'. + /// Looks up a localized string similar to stackalloc {0}[{1}]. /// internal static string IDS_StackAllocExpression { get { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index f5fc602aa24d18787b9fabc4ad0f3fd05bdd0161..590071e817fd37f30e4cb58521fd0a27ce90e9ab 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5196,6 +5196,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Conversion of a stackalloc expression of type '{0}' to type '{1}' is not possible. - stackalloc expression of type '{0}' + stackalloc {0}[{1}] \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs index e4e5de06656c52385ae17c03d8249cd2e58ad030..1eb427d13daad4492295bceb6b6e34df2fa037bb 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitConversion.cs @@ -83,10 +83,6 @@ private void EmitConversion(BoundConversion conversion) case ConversionKind.PointerToVoid: case ConversionKind.PointerToPointer: return; //no-op since they all have the same runtime representation - case ConversionKind.StackAllocToPointerType: - case ConversionKind.StackAllocToSpanType: - // no-op since operand (which contains the lowered expression) is already emitted - return; case ConversionKind.PointerToInteger: case ConversionKind.IntegerToPointer: var fromType = conversion.Operand.Type; diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index b8eef4c248ebea8db05bc7c534979fbe66a602ee..3ee126113cb883c739f4c5455162c02f3fd3a346 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -105,8 +105,8 @@ private void EmitExpressionCore(BoundExpression expression, bool used) EmitArrayCreationExpression((BoundArrayCreation)expression, used); break; - case BoundKind.StackAllocArrayCreation: - EmitStackAllocArrayCreationExpression((BoundStackAllocArrayCreation)expression, used); + case BoundKind.ConvertedStackAllocExpression: + EmitConvertedStackAllocExpression((BoundConvertedStackAllocExpression)expression, used); break; case BoundKind.Conversion: @@ -1826,7 +1826,7 @@ private void EmitArrayCreationExpression(BoundArrayCreation expression, bool use EmitPopIfUnused(used); } - private void EmitStackAllocArrayCreationExpression(BoundStackAllocArrayCreation expression, bool used) + private void EmitConvertedStackAllocExpression(BoundConvertedStackAllocExpression expression, bool used) { EmitExpression(expression.Count, used: true); _builder.EmitOpCode(ILOpCode.Localloc); diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index d2b5c7d1d3c5bc0cf3218a91c98b739f7c8841a7..a7866cef40996c4bcec6ddf519474c435dd9b835 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -1571,11 +1571,11 @@ public override BoundNode VisitCatchBlock(BoundCatchBlock node) return node.Update(node.Locals, exceptionSourceOpt, exceptionTypeOpt, boundFilter, boundBlock, node.IsSynthesizedAsyncCatchAll); } - public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreation node) + public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) { // CLI spec section 3.47 requires that the stack be empty when localloc occurs. EnsureOnlyEvalStack(); - return base.VisitStackAllocArrayCreation(node); + return base.VisitConvertedStackAllocExpression(node); } public override BoundNode VisitArrayInitialization(BoundArrayInitialization node) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 5cb442ba449540492a8552049232b2b49d9e712a..705bce0a2a3a3d81e75c165848728dada0dcc205 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -1923,17 +1923,6 @@ private static bool IsUserDefinedTrueOrFalse(BoundUnaryOperator @operator) type = ((BoundConvertedTupleLiteral)boundExpr).NaturalTypeOpt; break; } - case BoundKind.StackAllocArrayCreation: - { - Debug.Assert(boundExpr.Type is null); - Debug.Assert(highestBoundExpr.Kind == BoundKind.Conversion); - var boundConversion = (BoundConversion)highestBoundExpr; - - type = null; - convertedType = boundConversion.Type; - conversion = boundConversion.Conversion; - break; - } } } diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.NodeMapBuilder.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.NodeMapBuilder.cs index 343c504ae1aa776956c2bee3ccd454669e6459d3..c9ad43143f8cbe58888d97e599670113e9a5e4c9 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.NodeMapBuilder.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.NodeMapBuilder.cs @@ -224,17 +224,6 @@ public override BoundNode Visit(BoundNode node) /// The bound node. private bool ShouldAddNode(BoundNode currentBoundNode) { - // stackalloc conversion nodes should always be added, as information about their types exist on the conversion, not the operand node. - if (currentBoundNode.Kind == BoundKind.Conversion) - { - switch (((BoundConversion)currentBoundNode).Conversion.Kind) - { - case ConversionKind.StackAllocToPointerType: - case ConversionKind.StackAllocToSpanType: - return true; - } - } - // Do not add compiler generated nodes. if (currentBoundNode.WasCompilerGenerated) { diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 57bb658ed2941b7e599c1754e78752e15fa189f1..525037fd5ca0b7959001d5dbac74bb93bd35b6a6 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -2572,6 +2572,12 @@ public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreat return null; } + public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) + { + VisitRvalue(node.Count); + return null; + } + public override BoundNode VisitAnonymousObjectCreationExpression(BoundAnonymousObjectCreationExpression node) { // visit arguments as r-values diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 2bd778856bf2098ff600f02970cdef509038c362..18e2e7475774ed0199540f6281af150ecd0822fa 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -147,6 +147,7 @@ internal enum BoundKind: byte ArrayCreation, ArrayInitialization, StackAllocArrayCreation, + ConvertedStackAllocExpression, FieldAccess, HoistedFieldAccess, PropertyAccess, @@ -5227,21 +5228,18 @@ public BoundArrayInitialization Update(ImmutableArray initializ internal sealed partial class BoundStackAllocArrayCreation : BoundExpression { - public BoundStackAllocArrayCreation(SyntaxNode syntax, ConversionKind conversionKind, TypeSymbol elementType, BoundExpression count, TypeSymbol type, bool hasErrors = false) + public BoundStackAllocArrayCreation(SyntaxNode syntax, TypeSymbol elementType, BoundExpression count, TypeSymbol type, bool hasErrors = false) : base(BoundKind.StackAllocArrayCreation, syntax, type, hasErrors || count.HasErrors()) { Debug.Assert(elementType != null, "Field 'elementType' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); Debug.Assert(count != null, "Field 'count' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); - this.ConversionKind = conversionKind; this.ElementType = elementType; this.Count = count; } - public ConversionKind ConversionKind { get; } - public TypeSymbol ElementType { get; } public BoundExpression Count { get; } @@ -5251,11 +5249,50 @@ public override BoundNode Accept(BoundTreeVisitor visitor) return visitor.VisitStackAllocArrayCreation(this); } - public BoundStackAllocArrayCreation Update(ConversionKind conversionKind, TypeSymbol elementType, BoundExpression count, TypeSymbol type) + public BoundStackAllocArrayCreation Update(TypeSymbol elementType, BoundExpression count, TypeSymbol type) + { + if (elementType != this.ElementType || count != this.Count || type != this.Type) + { + var result = new BoundStackAllocArrayCreation(this.Syntax, elementType, count, type, this.HasErrors); + result.WasCompilerGenerated = this.WasCompilerGenerated; + return result; + } + return this; + } + } + + internal sealed partial class BoundConvertedStackAllocExpression : BoundExpression + { + public BoundConvertedStackAllocExpression(SyntaxNode syntax, TypeSymbol elementType, BoundExpression count, ConversionKind conversionKind, TypeSymbol type, bool hasErrors = false) + : base(BoundKind.ConvertedStackAllocExpression, syntax, type, hasErrors || count.HasErrors()) + { + + Debug.Assert(elementType != null, "Field 'elementType' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(count != null, "Field 'count' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + + this.ElementType = elementType; + this.Count = count; + this.ConversionKind = conversionKind; + } + + + public TypeSymbol ElementType { get; } + + public BoundExpression Count { get; } + + public ConversionKind ConversionKind { get; } + + public override BoundNode Accept(BoundTreeVisitor visitor) + { + return visitor.VisitConvertedStackAllocExpression(this); + } + + public BoundConvertedStackAllocExpression Update(TypeSymbol elementType, BoundExpression count, ConversionKind conversionKind, TypeSymbol type) { - if (conversionKind != this.ConversionKind || elementType != this.ElementType || count != this.Count || type != this.Type) + if (elementType != this.ElementType || count != this.Count || conversionKind != this.ConversionKind || type != this.Type) { - var result = new BoundStackAllocArrayCreation(this.Syntax, conversionKind, elementType, count, type, this.HasErrors); + var result = new BoundConvertedStackAllocExpression(this.Syntax, elementType, count, conversionKind, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -6350,6 +6387,8 @@ internal R VisitInternal(BoundNode node, A arg) return VisitArrayInitialization(node as BoundArrayInitialization, arg); case BoundKind.StackAllocArrayCreation: return VisitStackAllocArrayCreation(node as BoundStackAllocArrayCreation, arg); + case BoundKind.ConvertedStackAllocExpression: + return VisitConvertedStackAllocExpression(node as BoundConvertedStackAllocExpression, arg); case BoundKind.FieldAccess: return VisitFieldAccess(node as BoundFieldAccess, arg); case BoundKind.HoistedFieldAccess: @@ -6910,6 +6949,10 @@ public virtual R VisitStackAllocArrayCreation(BoundStackAllocArrayCreation node, { return this.DefaultVisit(node, arg); } + public virtual R VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node, A arg) + { + return this.DefaultVisit(node, arg); + } public virtual R VisitFieldAccess(BoundFieldAccess node, A arg) { return this.DefaultVisit(node, arg); @@ -7510,6 +7553,10 @@ public virtual BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreati { return this.DefaultVisit(node); } + public virtual BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) + { + return this.DefaultVisit(node); + } public virtual BoundNode VisitFieldAccess(BoundFieldAccess node) { return this.DefaultVisit(node); @@ -8259,6 +8306,11 @@ public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreat this.Visit(node.Count); return null; } + public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) + { + this.Visit(node.Count); + return null; + } public override BoundNode VisitFieldAccess(BoundFieldAccess node) { this.Visit(node.ReceiverOpt); @@ -9116,7 +9168,14 @@ public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreat BoundExpression count = (BoundExpression)this.Visit(node.Count); TypeSymbol elementType = this.VisitType(node.ElementType); TypeSymbol type = this.VisitType(node.Type); - return node.Update(node.ConversionKind, elementType, count, type); + return node.Update(elementType, count, type); + } + public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node) + { + BoundExpression count = (BoundExpression)this.Visit(node.Count); + TypeSymbol elementType = this.VisitType(node.ElementType); + TypeSymbol type = this.VisitType(node.Type); + return node.Update(elementType, count, node.ConversionKind, type); } public override BoundNode VisitFieldAccess(BoundFieldAccess node) { @@ -10566,13 +10625,23 @@ public override TreeDumperNode VisitStackAllocArrayCreation(BoundStackAllocArray { return new TreeDumperNode("stackAllocArrayCreation", null, new TreeDumperNode[] { - new TreeDumperNode("conversionKind", node.ConversionKind, null), new TreeDumperNode("elementType", node.ElementType, null), new TreeDumperNode("count", null, new TreeDumperNode[] { Visit(node.Count, null) }), new TreeDumperNode("type", node.Type, null) } ); } + public override TreeDumperNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression node, object arg) + { + return new TreeDumperNode("convertedStackAllocExpression", null, new TreeDumperNode[] + { + new TreeDumperNode("elementType", node.ElementType, null), + new TreeDumperNode("count", null, new TreeDumperNode[] { Visit(node.Count, null) }), + new TreeDumperNode("conversionKind", node.ConversionKind, null), + new TreeDumperNode("type", node.Type, null) + } + ); + } public override TreeDumperNode VisitFieldAccess(BoundFieldAccess node, object arg) { return new TreeDumperNode("fieldAccess", null, new TreeDumperNode[] diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index eb2c25783ddba1224b65f6aef037751ec781ac3e..191f3658c1fe25f7c13deca54e840f59961bd0f4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -181,11 +181,9 @@ private BoundExpression VisitExpressionImpl(BoundExpression node) // statement means that this constraint is not violated). // Dynamic type will be erased in emit phase. It is considered equivalent to Object in lowered bound trees. // Unused deconstructions are lowered to produce a return value that isn't a tuple type. - // stackalloc bound nodes have no type, and they are updated to the appropriate type after lowering Debug.Assert(visited == null || visited.HasErrors || ReferenceEquals(visited.Type, node.Type) || visited.Type.Equals(node.Type, TypeCompareKind.IgnoreDynamicAndTupleNames) || - IsUnusedDeconstruction(node) || - node.Kind == BoundKind.StackAllocArrayCreation); + IsUnusedDeconstruction(node)); return visited; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index ab292bbb180ee94f5f4e145f46efcc9b3cfdfb8a..7ce144e8870a630cbaed0dbca3d164b6fb35b9ef 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -21,17 +21,10 @@ public override BoundNode VisitConversion(BoundConversion node) } var rewrittenType = VisitType(node.Type); - var rewrittenOperand = node.Operand; - - if (rewrittenOperand is BoundStackAllocArrayCreation boundStackAlloc) - { - // Update operand node with the successful conversion kind - rewrittenOperand = boundStackAlloc.Update(node.ConversionKind, boundStackAlloc.ElementType, boundStackAlloc.Count, boundStackAlloc.Type); - } bool wasInExpressionLambda = _inExpressionLambda; _inExpressionLambda = _inExpressionLambda || (node.ConversionKind == ConversionKind.AnonymousFunction && !wasInExpressionLambda && rewrittenType.IsExpressionTree()); - rewrittenOperand = VisitExpression(rewrittenOperand); + var rewrittenOperand = VisitExpression(node.Operand); _inExpressionLambda = wasInExpressionLambda; var result = MakeConversionNode(node, node.Syntax, rewrittenOperand, node.Conversion, node.Checked, node.ExplicitCastInCode, node.ConstantValue, rewrittenType); @@ -204,13 +197,6 @@ private static bool IsFloatPointExpressionOfUnknownPrecision(BoundExpression rew } break; - case ConversionKind.StackAllocToPointerType: - case ConversionKind.StackAllocToSpanType: - { - var underlyingConversion = conversion.UnderlyingConversions.Single(); - return MakeConversionNode(oldNode, syntax, rewrittenOperand, underlyingConversion, @checked, explicitCastInCode, constantValueOpt, rewrittenType); - } - case ConversionKind.DefaultOrNullLiteral: if (!_inExpressionLambda || !explicitCastInCode) { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs index 746d1a733e0964c435d155458f34b786602216e1..2fee6a384051d731bd2adf879da959d422d7faa4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class LocalRewriter { - public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreation stackAllocNode) + public override BoundNode VisitConvertedStackAllocExpression(BoundConvertedStackAllocExpression stackAllocNode) { var rewrittenCount = VisitExpression(stackAllocNode.Count); @@ -21,17 +21,17 @@ public override BoundNode VisitStackAllocArrayCreation(BoundStackAllocArrayCreat case ConversionKind.StackAllocToPointerType: { var stackSize = RewriteStackAllocCountToSize(rewrittenCount, elementType); - var resultType = new PointerTypeSymbol(elementType); - - return stackAllocNode.Update(conversionKind, elementType, stackSize, resultType); + return stackAllocNode.Update(elementType, stackSize, conversionKind, stackAllocNode.Type); } case ConversionKind.StackAllocToSpanType: { - BoundLocal countTemp = _factory.StoreToTemp(rewrittenCount, out BoundAssignmentOperator countTempAssignment); - BoundExpression stackSize = RewriteStackAllocCountToSize(countTemp, elementType); - stackAllocNode = stackAllocNode.Update(conversionKind, elementType, stackSize, stackAllocNode.Type); + Debug.Assert(stackAllocNode.Type.IsSpanType()); + + var spanType = (NamedTypeSymbol)stackAllocNode.Type; + var countTemp = _factory.StoreToTemp(rewrittenCount, out BoundAssignmentOperator countTempAssignment); + var stackSize = RewriteStackAllocCountToSize(countTemp, elementType); + stackAllocNode = stackAllocNode.Update(elementType, stackSize, conversionKind, spanType); - var spanType = _compilation.GetWellKnownType(WellKnownType.System_Span_T).Construct(elementType); var spanCtor = (MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Span_T__ctor).SymbolAsMember(spanType); var ctorCall = _factory.New(spanCtor, stackAllocNode, countTemp); diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index 4b870f2aac2bb3e8bd721b956ebed11605779e63..41d785f1b8a4c1f6405c5b4c1f34bcb493c6372a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -389,7 +389,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) NeedsProxy(leftLocal.LocalSymbol)) { Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol)); - Debug.Assert(!IsStackAlloc(originalRight)); + Debug.Assert(originalRight.Kind != BoundKind.ConvertedStackAllocExpression); //spilling ref local variables throw ExceptionUtilities.Unreachable; } @@ -411,7 +411,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) // If the receiver of the field is on the stack when the stackalloc happens, // popping it will free the memory (?) or otherwise cause verification issues. // DevDiv Bugs 59454 - if (rewrittenLeft.Kind != BoundKind.Local && IsStackAlloc(originalRight)) + if (rewrittenLeft.Kind != BoundKind.Local && originalRight.Kind == BoundKind.ConvertedStackAllocExpression) { // From ILGENREC::genAssign: // DevDiv Bugs 59454: Handle hoisted local initialized with a stackalloc @@ -436,13 +436,6 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) return node.Update(rewrittenLeft, rewrittenRight, node.RefKind, rewrittenType); } - private static bool IsStackAlloc(BoundExpression expr) - { - return - expr.Kind == BoundKind.StackAllocArrayCreation || - expr.Kind == BoundKind.Conversion && ((BoundConversion)expr).Operand.Kind == BoundKind.StackAllocArrayCreation; - } - public override BoundNode VisitFieldInfo(BoundFieldInfo node) { var rewrittenField = ((FieldSymbol)node.Field.OriginalDefinition) diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 84f5459db98f47254378b9be1ce47d1fb269e62f..92ff9b6b8c596aa4632ffcd15d92cb7aa6818fbd 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -3,3 +3,4 @@ Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.ReadOnlyKeyword.get -> Micros Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefType(Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.RefTypeSyntax +Microsoft.CodeAnalysis.CSharp.Conversion.IsStackAlloc.get -> bool \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index 0132c5631bedcbc323763285102cb1c83d29f8c4..d465acd5709a45e7234661d87ba7942966dff9eb 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -150,15 +150,18 @@ internal static bool IsLegalSpanStackAllocPosition(this SyntaxNode node) switch (parentNode.Kind()) { + // In case of a declaration of a Span variable case SyntaxKind.EqualsValueClause: { - // In case of a declaration of a Span variable - return parentNode.Parent.IsKind(SyntaxKind.VariableDeclarator); + SyntaxNode variableDeclarator = parentNode.Parent; + + return variableDeclarator.IsKind(SyntaxKind.VariableDeclarator) && + variableDeclarator.Parent.IsKind(SyntaxKind.VariableDeclaration); } + // In case of reassignment to a Span variable case SyntaxKind.SimpleAssignmentExpression: { - // In case of reassignment to a Span variable - return true; + return parentNode.Parent.IsKind(SyntaxKind.ExpressionStatement); } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs index 4d73f31037e40a71f4660076f4b569b3000272d4..a48f2d5df7080ae0e8e34b7c76decb9a9926a394 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs @@ -15668,5 +15668,173 @@ .maxstack 1 }"); } + [Fact] + public void CorrectOverloadOfStackAllocSpanChosen() + { + var comp = CreateCompilationWithMscorlibAndSpan(@" +using System; +class Test +{ + unsafe public static void Main() + { + bool condition = false; + + var span1 = condition ? stackalloc int[1] : new Span(null, 2); + Console.Write(span1.Length); + + var span2 = condition ? new Span(null, 3) : stackalloc int[4]; + Console.Write(span2.Length); + } +}", TestOptions.UnsafeReleaseExe); + + CompileAndVerify(comp, expectedOutput: "24"); + } + + [Fact] + public void StackAllocExpressionIL() + { + var comp = CreateCompilationWithMscorlibAndSpan(@" +using System; +class Test +{ + public static void Main() + { + Span x = stackalloc int[10]; + Console.WriteLine(x.Length); + } +}", TestOptions.ReleaseExe); + + CompileAndVerify(comp, expectedOutput: "10", verify: false).VerifyIL("Test.Main", @" +{ + // Code size 29 (0x1d) + .maxstack 2 + .locals init (System.Span V_0, //x + int V_1) + IL_0000: ldc.i4.s 10 + IL_0002: stloc.1 + IL_0003: ldloc.1 + IL_0004: conv.u + IL_0005: ldc.i4.4 + IL_0006: mul.ovf.un + IL_0007: localloc + IL_0009: ldloc.1 + IL_000a: newobj ""System.Span..ctor(void*, int)"" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: call ""int System.Span.Length.get"" + IL_0017: call ""void System.Console.WriteLine(int)"" + IL_001c: ret +}"); + } + + [Fact] + public void StackAllocSpanLengthNotEvaluatedTwice() + { + var comp = CreateCompilationWithMscorlibAndSpan(@" +using System; +class Test +{ + private static int length = 0; + + private static int GetLength() + { + return ++length; + } + + public static void Main() + { + for (int i = 0; i < 5; i++) + { + Span x = stackalloc int[GetLength()]; + Console.Write(x.Length); + } + } +}", TestOptions.ReleaseExe); + + CompileAndVerify(comp, expectedOutput: "12345", verify: false).VerifyIL("Test.Main", @" +{ + // Code size 44 (0x2c) + .maxstack 2 + .locals init (int V_0, //i + System.Span V_1, //x + int V_2) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: br.s IL_0027 + IL_0004: call ""int Test.GetLength()"" + IL_0009: stloc.2 + IL_000a: ldloc.2 + IL_000b: conv.u + IL_000c: ldc.i4.4 + IL_000d: mul.ovf.un + IL_000e: localloc + IL_0010: ldloc.2 + IL_0011: newobj ""System.Span..ctor(void*, int)"" + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: call ""int System.Span.Length.get"" + IL_001e: call ""void System.Console.Write(int)"" + IL_0023: ldloc.0 + IL_0024: ldc.i4.1 + IL_0025: add + IL_0026: stloc.0 + IL_0027: ldloc.0 + IL_0028: ldc.i4.5 + IL_0029: blt.s IL_0004 + IL_002b: ret +}"); + } + + [Fact] + public void ImplicitCastOperatorOnStackAllocIsLoweredCorrectly() + { + var comp = CreateCompilationWithMscorlibAndSpan(@" +using System; +unsafe class Test +{ + public static void Main() + { + Test obj1 = stackalloc int[10]; + Console.Write(""|""); + Test obj2 = stackalloc double[10]; + } + + public static implicit operator Test(Span value) + { + Console.Write(""SpanOpCalled""); + return default(Test); + } + + public static implicit operator Test(double* value) + { + Console.Write(""PointerOpCalled""); + return default(Test); + } +}", TestOptions.UnsafeReleaseExe); + + CompileAndVerify(comp, expectedOutput: "SpanOpCalled|PointerOpCalled", verify: false); + } + + [Fact] + public void ExplicitCastOperatorOnStackAllocIsLoweredCorrectly() + { + var comp = CreateCompilationWithMscorlibAndSpan(@" +using System; +unsafe class Test +{ + public static void Main() + { + Test obj1 = (Test)stackalloc int[10]; + } + + public static explicit operator Test(Span value) + { + Console.Write(""SpanOpCalled""); + return default(Test); + } +}", TestOptions.UnsafeReleaseExe); + + CompileAndVerify(comp, expectedOutput: "SpanOpCalled", verify: false); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs index d99e6f8e45b1e2030d160df6a4fe2c27e0eb03c5..6d54fd30cc770a64217c3657fe646ba627e55dbe 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StackAllocSpanExpressionsTests.cs @@ -29,6 +29,7 @@ public void Method() var obj2 = stackalloc int[10]; Span obj3 = stackalloc int[10]; int* obj4 = stackalloc int[10]; + double* obj5 = stackalloc int[10]; } public static implicit operator Test(int* value) @@ -37,51 +38,56 @@ public void Method() } }", TestOptions.UnsafeReleaseDll); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (11,24): error CS8520: Conversion of a stackalloc expression of type 'int' to type 'double*' is not possible. + // double* obj5 = stackalloc int[10]; + Diagnostic(ErrorCode.ERR_StackAllocConversionNotPossible, "stackalloc int[10]").WithArguments("int", "double*").WithLocation(11, 24)); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree); var variables = tree.GetCompilationUnitRoot().DescendantNodes().OfType(); - Assert.Equal(4, variables.Count()); + Assert.Equal(5, variables.Count()); var obj1 = variables.ElementAt(0); Assert.Equal("obj1", obj1.Identifier.Text); var obj1Value = model.GetSemanticInfoSummary(obj1.Initializer.Value); - Assert.Null(obj1Value.Type); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj1Value.Type).PointedAtType.SpecialType); Assert.Equal("Test", obj1Value.ConvertedType.Name); - Assert.Equal(ConversionKind.StackAllocToPointerType, obj1Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.ImplicitUserDefined, obj1Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(ConversionKind.ImplicitUserDefined, obj1Value.ImplicitConversion.Kind); var obj2 = variables.ElementAt(1); Assert.Equal("obj2", obj2.Identifier.Text); var obj2Value = model.GetSemanticInfoSummary(obj2.Initializer.Value); - Assert.Null(obj2Value.Type); - Assert.True(obj2Value.ConvertedType is PointerTypeSymbol); - Assert.Equal("Int32", ((PointerTypeSymbol)obj2Value.ConvertedType).PointedAtType.Name); - Assert.Equal(ConversionKind.StackAllocToPointerType, obj2Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj2Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj2Value.Type).PointedAtType.SpecialType); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj2Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.Identity, obj2Value.ImplicitConversion.Kind); var obj3 = variables.ElementAt(2); Assert.Equal("obj3", obj3.Identifier.Text); var obj3Value = model.GetSemanticInfoSummary(obj3.Initializer.Value); - Assert.Null(obj3Value.Type); + Assert.Equal("Span", obj3Value.Type.Name); Assert.Equal("Span", obj3Value.ConvertedType.Name); - Assert.Equal(ConversionKind.StackAllocToSpanType, obj3Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj3Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(ConversionKind.Identity, obj3Value.ImplicitConversion.Kind); var obj4 = variables.ElementAt(3); Assert.Equal("obj4", obj4.Identifier.Text); var obj4Value = model.GetSemanticInfoSummary(obj4.Initializer.Value); - Assert.Null(obj4Value.Type); - Assert.True(obj4Value.ConvertedType is PointerTypeSymbol); - Assert.Equal("Int32", ((PointerTypeSymbol)obj4Value.ConvertedType).PointedAtType.Name); - Assert.Equal(ConversionKind.StackAllocToPointerType, obj4Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj4Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj4Value.Type).PointedAtType.SpecialType); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj4Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.Identity, obj4Value.ImplicitConversion.Kind); + + var obj5 = variables.ElementAt(4); + Assert.Equal("obj5", obj5.Identifier.Text); + + var obj5Value = model.GetSemanticInfoSummary(obj5.Initializer.Value); + Assert.Null(obj5Value.Type); + Assert.Equal(SpecialType.System_Double, ((PointerTypeSymbol)obj5Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.NoConversion, obj5Value.ImplicitConversion.Kind); } [Fact] @@ -97,6 +103,7 @@ public void Method() var obj2 = stackalloc int[10]; Span obj3 = stackalloc int[10]; int* obj4 = stackalloc int[10]; + double* obj5 = stackalloc int[10]; } public static explicit operator Test(Span value) @@ -105,52 +112,57 @@ public void Method() } }", TestOptions.UnsafeReleaseDll); - comp.VerifyDiagnostics(); + comp.VerifyDiagnostics( + // (11,24): error CS8520: Conversion of a stackalloc expression of type 'int' to type 'double*' is not possible. + // double* obj5 = stackalloc int[10]; + Diagnostic(ErrorCode.ERR_StackAllocConversionNotPossible, "stackalloc int[10]").WithArguments("int", "double*").WithLocation(11, 24)); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree); var variables = tree.GetCompilationUnitRoot().DescendantNodes().OfType(); - Assert.Equal(4, variables.Count()); + Assert.Equal(5, variables.Count()); var obj1 = variables.ElementAt(0); Assert.Equal("obj1", obj1.Identifier.Text); Assert.Equal(SyntaxKind.CastExpression, obj1.Initializer.Value.Kind()); var obj1Value = model.GetSemanticInfoSummary(((CastExpressionSyntax)obj1.Initializer.Value).Expression); - Assert.Null(obj1Value.Type); + Assert.Equal("Span", obj1Value.Type.Name); Assert.Equal("Span", obj1Value.ConvertedType.Name); - Assert.Equal(ConversionKind.StackAllocToSpanType, obj1Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj1Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(ConversionKind.Identity, obj1Value.ImplicitConversion.Kind); var obj2 = variables.ElementAt(1); Assert.Equal("obj2", obj2.Identifier.Text); var obj2Value = model.GetSemanticInfoSummary(obj2.Initializer.Value); - Assert.Null(obj2Value.Type); - Assert.True(obj2Value.ConvertedType is PointerTypeSymbol); - Assert.Equal("Int32", ((PointerTypeSymbol)obj2Value.ConvertedType).PointedAtType.Name); - Assert.Equal(ConversionKind.StackAllocToPointerType, obj2Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj2Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj2Value.Type).PointedAtType.SpecialType); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj2Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.Identity, obj2Value.ImplicitConversion.Kind); var obj3 = variables.ElementAt(2); Assert.Equal("obj3", obj3.Identifier.Text); var obj3Value = model.GetSemanticInfoSummary(obj3.Initializer.Value); - Assert.Null(obj3Value.Type); + Assert.Equal("Span", obj3Value.Type.Name); Assert.Equal("Span", obj3Value.ConvertedType.Name); - Assert.Equal(ConversionKind.StackAllocToSpanType, obj3Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj3Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(ConversionKind.Identity, obj3Value.ImplicitConversion.Kind); var obj4 = variables.ElementAt(3); Assert.Equal("obj4", obj4.Identifier.Text); var obj4Value = model.GetSemanticInfoSummary(obj4.Initializer.Value); - Assert.Null(obj4Value.Type); - Assert.True(obj4Value.ConvertedType is PointerTypeSymbol); - Assert.Equal("Int32", ((PointerTypeSymbol)obj4Value.ConvertedType).PointedAtType.Name); - Assert.Equal(ConversionKind.StackAllocToPointerType, obj4Value.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.Identity, obj4Value.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj4Value.Type).PointedAtType.SpecialType); + Assert.Equal(SpecialType.System_Int32, ((PointerTypeSymbol)obj4Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.Identity, obj4Value.ImplicitConversion.Kind); + + var obj5 = variables.ElementAt(4); + Assert.Equal("obj5", obj5.Identifier.Text); + + var obj5Value = model.GetSemanticInfoSummary(obj5.Initializer.Value); + Assert.Null(obj5Value.Type); + Assert.Equal(SpecialType.System_Double, ((PointerTypeSymbol)obj5Value.ConvertedType).PointedAtType.SpecialType); + Assert.Equal(ConversionKind.NoConversion, obj5Value.ImplicitConversion.Kind); } [Fact] @@ -225,9 +237,9 @@ void M() var x = true ? stackalloc int [10] : stackalloc int [5]; } }", TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (6,17): error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'stackalloc expression of type 'int'' and 'stackalloc expression of type 'int'' + // (6,17): error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'stackalloc int[10]' and 'stackalloc int[5]' // var x = true ? stackalloc int [10] : stackalloc int [5]; - Diagnostic(ErrorCode.ERR_InvalidQM, "true ? stackalloc int [10] : stackalloc int [5]").WithArguments("stackalloc expression of type 'int'", "stackalloc expression of type 'int'").WithLocation(6, 17)); + Diagnostic(ErrorCode.ERR_InvalidQM, "true ? stackalloc int [10] : stackalloc int [5]").WithArguments("stackalloc int[10]", "stackalloc int[5]").WithLocation(6, 17)); } [Fact] @@ -289,9 +301,9 @@ void M() var x = true ? stackalloc int [10] : a; } }", TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (8,17): error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'stackalloc expression of type 'int'' and 'Span' + // (8,17): error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'stackalloc int[10]' and 'Span' // var x = true ? stackalloc int [10] : a; - Diagnostic(ErrorCode.ERR_InvalidQM, "true ? stackalloc int [10] : a").WithArguments("stackalloc expression of type 'int'", "System.Span").WithLocation(8, 17)); + Diagnostic(ErrorCode.ERR_InvalidQM, "true ? stackalloc int [10] : a").WithArguments("stackalloc int[10]", "System.Span").WithLocation(8, 17)); } [Fact] @@ -305,9 +317,9 @@ void M() if(stackalloc int[10] == stackalloc int[10]) { } } }", TestOptions.UnsafeReleaseDll).VerifyDiagnostics( - // (6,12): error CS0019: Operator '==' cannot be applied to operands of type 'stackalloc expression of type 'int'' and 'stackalloc expression of type 'int'' + // (6,12): error CS0019: Operator '==' cannot be applied to operands of type 'stackalloc int[10]' and 'stackalloc int[10]' // if(stackalloc int[10] == stackalloc int[10]) { } - Diagnostic(ErrorCode.ERR_BadBinaryOps, "stackalloc int[10] == stackalloc int[10]").WithArguments("==", "stackalloc expression of type 'int'", "stackalloc expression of type 'int'").WithLocation(6, 12)); + Diagnostic(ErrorCode.ERR_BadBinaryOps, "stackalloc int[10] == stackalloc int[10]").WithArguments("==", "stackalloc int[10]", "stackalloc int[10]").WithLocation(6, 12)); } [Fact] @@ -463,9 +475,9 @@ void M() } "; CreateCompilationWithMscorlibAndSpan(test, TestOptions.ReleaseDll).VerifyDiagnostics( - // (6,22): error CS0023: Operator '.' cannot be applied to operand of type 'stackalloc expression of type 'int'' + // (6,22): error CS0023: Operator '.' cannot be applied to operand of type 'stackalloc int[10]' // int length = (stackalloc int [10]).Length; - Diagnostic(ErrorCode.ERR_BadUnaryOp, "(stackalloc int [10]).Length").WithArguments(".", "stackalloc expression of type 'int'").WithLocation(6, 22)); + Diagnostic(ErrorCode.ERR_BadUnaryOp, "(stackalloc int [10]).Length").WithArguments(".", "stackalloc int[10]").WithLocation(6, 22)); } [Fact] @@ -487,9 +499,9 @@ static void Main() } "; CreateCompilationWithMscorlibAndSpan(test, TestOptions.UnsafeReleaseExe).VerifyDiagnostics( - // (7,16): error CS1503: Argument 1: cannot convert from 'stackalloc expression of type 'int'' to 'Span' + // (7,16): error CS1503: Argument 1: cannot convert from 'stackalloc int[10]' to 'Span' // Invoke(stackalloc int [10]); - Diagnostic(ErrorCode.ERR_BadArgType, "stackalloc int [10]").WithArguments("1", "stackalloc expression of type 'int'", "System.Span").WithLocation(7, 16)); + Diagnostic(ErrorCode.ERR_BadArgType, "stackalloc int [10]").WithArguments("1", "stackalloc int[10]", "System.Span").WithLocation(7, 16)); } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs index 514af35ab5f6be3b784c4fd610ea80bb153ec02d..b22a5c1ace01f7864949512e5da2342246966fd3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UnsafeTests.cs @@ -2061,8 +2061,7 @@ unsafe void Test() "; var expected = @" No, TypeExpression 'int*' is not a non-moveable variable -Yes, Conversion 'stackalloc int[1]' is a non-moveable variable -Yes, StackAllocArrayCreation 'stackalloc int[1]' is a non-moveable variable +Yes, ConvertedStackAllocExpression 'stackalloc int[1]' is a non-moveable variable No, Literal '1' is not a non-moveable variable ".Trim(); @@ -7620,10 +7619,9 @@ unsafe static void Main() var countSyntax = arrayTypeSyntax.RankSpecifiers.Single().Sizes.Single(); var stackAllocSummary = model.GetSemanticInfoSummary(stackAllocSyntax); - Assert.Null(stackAllocSummary.Type); + Assert.Equal(SpecialType.System_Char, ((PointerTypeSymbol)stackAllocSummary.Type).PointedAtType.SpecialType); Assert.Equal(SpecialType.System_Void, ((PointerTypeSymbol)stackAllocSummary.ConvertedType).PointedAtType.SpecialType); - Assert.Equal(ConversionKind.StackAllocToPointerType, stackAllocSummary.ImplicitConversion.Kind); - Assert.Equal(ConversionKind.PointerToVoid, stackAllocSummary.ImplicitConversion.UnderlyingConversions.Single().Kind); + Assert.Equal(Conversion.PointerToVoid, stackAllocSummary.ImplicitConversion); Assert.Null(stackAllocSummary.Symbol); Assert.Equal(0, stackAllocSummary.CandidateSymbols.Length); Assert.Equal(CandidateReason.None, stackAllocSummary.CandidateReason);