diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 174cd7d4b74ea..2b64d111af69a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1958,6 +1958,27 @@ internal static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint return false; } + internal static uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression) + { + uint broadest = scopeOfTheContainingExpression; + foreach (var element in expr.Arguments) + { + uint valEscape; + if (element.Kind == BoundKind.TupleLiteral) + { + valEscape = GetBroadestValEscape((BoundTupleExpression)element, scopeOfTheContainingExpression); + } + else + { + valEscape = GetValEscape(element, scopeOfTheContainingExpression); + } + + broadest = Math.Min(broadest, valEscape); + } + + return broadest; + } + /// /// Computes the widest scope depth to which the given expression can escape by value. /// @@ -1993,6 +2014,14 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin // always returnable return Binder.ExternalScope; + case BoundKind.TupleLiteral: + var tupleLiteral = (BoundTupleLiteral)expr; + return GetTupleValEscape(tupleLiteral.Arguments, scopeOfTheContainingExpression); + + case BoundKind.ConvertedTupleLiteral: + var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr; + return GetTupleValEscape(convertedTupleLiteral.Arguments, scopeOfTheContainingExpression); + case BoundKind.MakeRefOperator: case BoundKind.RefValueOperator: // for compat reasons @@ -2004,6 +2033,9 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin // same as uninitialized local return Binder.ExternalScope; + case BoundKind.DeconstructValuePlaceholder: + return ((BoundDeconstructValuePlaceholder)expr).ValEscape; + case BoundKind.Local: return ((BoundLocal)expr).LocalSymbol.ValEscapeScope; @@ -2166,6 +2198,17 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin } } + private static uint GetTupleValEscape(ImmutableArray elements, uint scopeOfTheContainingExpression) + { + uint narrowestScope = scopeOfTheContainingExpression; + foreach (var element in elements) + { + narrowestScope = Math.Max(narrowestScope, GetValEscape(element, scopeOfTheContainingExpression)); + } + + return narrowestScope; + } + private static uint GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint scopeOfTheContainingExpression) { var result = Binder.ExternalScope; @@ -2240,6 +2283,14 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint // always returnable return true; + case BoundKind.TupleLiteral: + var tupleLiteral = (BoundTupleLiteral)expr; + return CheckTupleValEscape(tupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics); + + case BoundKind.ConvertedTupleLiteral: + var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr; + return CheckTupleValEscape(convertedTupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics); + case BoundKind.MakeRefOperator: case BoundKind.RefValueOperator: // for compat reasons @@ -2249,6 +2300,15 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint // same as uninitialized local return true; + case BoundKind.DeconstructValuePlaceholder: + var placeholder = (BoundDeconstructValuePlaceholder)expr; + if (placeholder.ValEscape > escapeTo) + { + Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, placeholder.Syntax); + return false; + } + return true; + case BoundKind.Local: var localSymbol = ((BoundLocal)expr).LocalSymbol; if (localSymbol.ValEscapeScope > escapeTo) @@ -2463,8 +2523,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint // case BoundKind.NameOfOperator: // case BoundKind.InterpolatedString: // case BoundKind.StringInsert: -// case BoundKind.TupleLiteral: -// case BoundKind.ConvertedTupleLiteral: // case BoundKind.DynamicIndexerAccess: // case BoundKind.Lambda: // case BoundKind.DynamicObjectCreationExpression: @@ -2541,7 +2599,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint // case BoundKind.DeclarationPattern: // case BoundKind.ConstantPattern: // case BoundKind.WildcardPattern: -// case BoundKind.DeconstructValuePlaceholder: #endregion @@ -2577,6 +2634,19 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint } } + private static bool CheckTupleValEscape(ImmutableArray elements, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics) + { + foreach (var element in elements) + { + if (!CheckValEscape(element.Syntax, element, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics)) + { + return false; + } + } + + return true; + } + private static bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics) { foreach (var expression in initExpr.Initializers) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 54f1486efd020..1626b4610e51a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -95,13 +94,15 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia DeconstructionVariable locals = BindDeconstructionVariables(left, diagnostics, ref declaration, ref expression); Debug.Assert(locals.HasNestedVariables); - BoundExpression boundRight = rightPlaceholder ?? BindValue(right, diagnostics, BindValueKind.RValue); - boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, diagnostics); + var deconstructionDiagnostics = new DiagnosticBag(); + BoundExpression boundRight = rightPlaceholder ?? BindValue(right, deconstructionDiagnostics, BindValueKind.RValue); + boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics); bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left); - var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, diagnostics); + var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, deconstructionDiagnostics); DeconstructionVariable.FreeDeconstructionVariables(locals.NestedVariables); + diagnostics.AddRange(deconstructionDiagnostics); return assignment; } @@ -113,10 +114,12 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia bool resultIsUsed, DiagnosticBag diagnostics) { + uint rightEscape = GetValEscape(boundRHS, this.LocalScopeDepth); + if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType()) { // we could still not infer a type for the RHS - FailRemainingInferences(checkedVariables, diagnostics); + FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); var type = boundRHS.Type ?? voidType; @@ -139,11 +142,14 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia checkedVariables, out conversion); - FailRemainingInferences(checkedVariables, diagnostics); + FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); - var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed); + var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed); TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type; + uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth); + boundRHS = ValidateEscape(boundRHS, leftEscape, isByRef: false, diagnostics: diagnostics); + var boundConversion = new BoundConversion( boundRHS.Syntax, boundRHS, @@ -197,7 +203,10 @@ private BoundExpression FixTupleLiteral(ArrayBuilder che // Let's fix the literal up by figuring out its type // For declarations, that means merging type information from the LHS and RHS // For assignments, only the LHS side matters since it is necessarily typed - TypeSymbol mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, Compilation, diagnostics); + + // If we already have diagnostics at this point, it is not worth collecting likely duplicate diagnostics from making the merged type + bool hadErrors = diagnostics.HasAnyErrors(); + TypeSymbol mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, Compilation, hadErrors ? null : diagnostics); if ((object)mergedTupleType != null) { boundRHS = GenerateConversionForAssignment(mergedTupleType, boundRHS, diagnostics); @@ -248,7 +257,7 @@ private BoundExpression FixTupleLiteral(ArrayBuilder che else { ImmutableArray outPlaceholders; - var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, type); + var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type); var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, inputPlaceholder, rightSyntax, diagnostics, out outPlaceholders); @@ -345,8 +354,10 @@ private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol t /// /// Find any deconstruction locals that are still pending inference and fail their inference. + /// Set the safe-to-escape scope for all deconstruction locals. /// - private void FailRemainingInferences(ArrayBuilder variables, DiagnosticBag diagnostics) + private void FailRemainingInferencesAndSetValEscape(ArrayBuilder variables, DiagnosticBag diagnostics, + uint rhsValEscape) { int count = variables.Count; for (int i = 0; i < count; i++) @@ -354,15 +365,22 @@ private void FailRemainingInferences(ArrayBuilder variab var variable = variables[i]; if (variable.HasNestedVariables) { - FailRemainingInferences(variable.NestedVariables, diagnostics); + FailRemainingInferencesAndSetValEscape(variable.NestedVariables, diagnostics, rhsValEscape); } else { switch (variable.Single.Kind) { + case BoundKind.Local: + var local = (BoundLocal)variable.Single; + if (local.IsDeclaration) + { + ((SourceLocalSymbol)local.LocalSymbol).SetValEscape(rhsValEscape); + } + break; case BoundKind.DeconstructionVariablePendingInference: - BoundExpression local = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics); - variables[i] = new DeconstructionVariable(local, local.Syntax); + BoundExpression errorLocal = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics); + variables[i] = new DeconstructionVariable(errorLocal, errorLocal.Syntax); break; case BoundKind.DiscardExpression: var pending = (BoundDiscardExpression)variable.Single; @@ -439,6 +457,7 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder.GetInstance(leftLength); + var locationsBuilder = ArrayBuilder.GetInstance(leftLength); for (int i = 0; i < rightLength; i++) { BoundExpression element = rhsLiteral.Arguments[i]; @@ -479,29 +498,29 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder t == null)) { typesBuilder.Free(); + locationsBuilder.Free(); return null; } // The tuple created here is not identical to the one created by // DeconstructionVariablesAsTuple. It represents a smaller // tree of types used for figuring out natural types in tuple literal. - // Therefore, we do not check constraints here as it would report errors - // that are already reported later. DeconstructionVariablesAsTuple - // constructs the final tuple type and checks constraints. return TupleTypeSymbol.Create( locationOpt: null, elementTypes: typesBuilder.ToImmutableAndFree(), - elementLocations: default(ImmutableArray), + elementLocations: locationsBuilder.ToImmutableAndFree(), elementNames: default(ImmutableArray), compilation: compilation, diagnostics: diagnostics, - shouldCheckConstraints: false, - errorPositions: default(ImmutableArray)); + shouldCheckConstraints: true, + errorPositions: default(ImmutableArray), + syntax: syntax); } private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder variables, @@ -672,7 +691,7 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres out ImmutableArray outPlaceholders, BoundExpression childNode) { Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type, numParameters); - outPlaceholders = default(ImmutableArray); + outPlaceholders = default; return BadExpression(rightSyntax, childNode); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ea62ee8248fb3..cc24be3b3d183 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -2536,7 +2536,7 @@ private BoundExpression BindArgumentExpression(DiagnosticBag diagnostics, Expres else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference) { TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg); - arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, success: true); + arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, this, success: true); } else if (argument.Kind == BoundKind.DiscardExpression && !argument.HasExpressionType()) { diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 1509e0135173d..c6142b9f99100 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -168,7 +168,10 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable; - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, inferredType ?? CreateErrorType("var")); + + // Tracking narrowest safe-to-escape scope by default, the proper val escape will be set when doing full binding of the foreach statement + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, this.LocalScopeDepth, inferredType ?? CreateErrorType("var")); + DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( @@ -202,6 +205,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, BoundTypeExpression boundIterationVariableType; bool hasNameConflicts = false; BoundForEachDeconstructStep deconstructStep = null; + uint collectionEscape = GetValEscape(collectionExpr, this.LocalScopeDepth); switch (_syntax.Kind()) { case SyntaxKind.ForEachStatement: @@ -230,7 +234,11 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, } boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType); - this.IterationVariable.SetType(iterationVariableType); + + SourceLocalSymbol local = this.IterationVariable; + local.SetType(iterationVariableType); + local.SetValEscape(collectionEscape); + break; } case SyntaxKind.ForEachVariableStatement: @@ -241,7 +249,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, var variables = node.Variable; if (variables.IsDeconstructionLeft()) { - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, iterationVariableType); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, iterationVariableType); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( @@ -283,8 +291,8 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, // I.E. - they will be considered declared and assigned in each iteration step. ImmutableArray iterationVariables = this.Locals; - Debug.Assert(hasErrors || - _syntax.HasErrors || + Debug.Assert(hasErrors || + _syntax.HasErrors || iterationVariables.All(local => local.DeclarationKind == LocalDeclarationKind.ForEachIterationVariable), "Should not have iteration variables that are not ForEachIterationVariable in valid code"); @@ -349,8 +357,8 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics, var getEnumeratorType = builder.GetEnumeratorMethod.ReturnType; // we never convert struct enumerators to object - it is done only for null-checks. - builder.EnumeratorConversion = getEnumeratorType.IsValueType? - Conversion.Identity: + builder.EnumeratorConversion = getEnumeratorType.IsValueType ? + Conversion.Identity : this.Conversions.ClassifyConversionFromType(getEnumeratorType, GetSpecialType(SpecialType.System_Object, diagnostics, _syntax), ref useSiteDiagnostics); if (getEnumeratorType.IsRestrictedType() && (IsDirectlyInIterator || IsInAsyncMethod())) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 4870d799e10fe..96deb6d2df570 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -87,6 +87,7 @@ It is used to perform intermediate binding, and will not survive the local rewriting. --> + diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs index c75c907c874dc..8df2244782a60 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -9,17 +9,18 @@ internal partial class OutDeconstructVarPendingInference { public BoundDeconstructValuePlaceholder Placeholder; - public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, bool success) + public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, Binder binder, bool success) { - Debug.Assert((object)Placeholder == null); + Debug.Assert(Placeholder is null); - Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, type, hasErrors: this.HasErrors || !success); + // The val escape scope for this placeholder won't be used, so defaulting to narrowest scope + Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, binder.LocalScopeDepth, type, hasErrors: this.HasErrors || !success); return Placeholder; } public BoundDeconstructValuePlaceholder FailInference(Binder binder) { - return SetInferredType(binder.CreateErrorType(), success: false); + return SetInferredType(binder.CreateErrorType(), binder, success: false); } } } diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index e40c42464aacb..f68d708cb5def 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -412,33 +412,37 @@ protected BoundValuePlaceholderBase(BoundKind kind, SyntaxNode syntax, TypeSymbo internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase { - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, TypeSymbol type, bool hasErrors) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type, bool hasErrors) : base(BoundKind.DeconstructValuePlaceholder, syntax, type, hasErrors) { Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + this.ValEscape = valEscape; } - public BoundDeconstructValuePlaceholder(SyntaxNode syntax, TypeSymbol type) + public BoundDeconstructValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type) : base(BoundKind.DeconstructValuePlaceholder, syntax, type) { Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)"); + this.ValEscape = valEscape; } + public uint ValEscape { get; } + public override BoundNode Accept(BoundTreeVisitor visitor) { return visitor.VisitDeconstructValuePlaceholder(this); } - public BoundDeconstructValuePlaceholder Update(TypeSymbol type) + public BoundDeconstructValuePlaceholder Update(uint valEscape, TypeSymbol type) { - if (type != this.Type) + if (valEscape != this.ValEscape || type != this.Type) { - var result = new BoundDeconstructValuePlaceholder(this.Syntax, type, this.HasErrors); + var result = new BoundDeconstructValuePlaceholder(this.Syntax, valEscape, type, this.HasErrors); result.WasCompilerGenerated = this.WasCompilerGenerated; return result; } @@ -8466,7 +8470,7 @@ public override BoundNode VisitGlobalStatementInitializer(BoundGlobalStatementIn public override BoundNode VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node) { TypeSymbol type = this.VisitType(node.Type); - return node.Update(type); + return node.Update(node.ValEscape, type); } public override BoundNode VisitDup(BoundDup node) { @@ -9377,6 +9381,7 @@ public override TreeDumperNode VisitDeconstructValuePlaceholder(BoundDeconstruct { return new TreeDumperNode("deconstructValuePlaceholder", null, new TreeDumperNode[] { + new TreeDumperNode("valEscape", node.ValEscape, null), new TreeDumperNode("type", node.Type, null) } ); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index 344deda279ac5..77dac53792fde 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -244,7 +244,7 @@ internal virtual void SetRefEscape(uint value) internal virtual void SetValEscape(uint value) { - throw ExceptionUtilities.Unreachable; + _valEscapeScope = value; } public override Symbol ContainingSymbol diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index dbeefa50f40ea..412d1b25cf5dc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2386,5 +2386,667 @@ .maxstack 1 } "); } + + [Fact] + public void DeconstructionAssignmentToGlobal() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + (global, global) = global; + (global, global) = local; // error 1 + (global, local) = local; // error 2 + (local, local) = local; + (global, _) = local; // error 3 + (local, _) = local; // error 4 + (global, _) = global; + } + public static void Main() + { + } +} +public static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) + { + throw null; + } +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (10,28): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, global) = local; // error 1 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(10, 28), + // (11,27): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, local) = local; // error 2 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(11, 27), + // (13,23): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, _) = local; // error 3 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(13, 23), + // (14,22): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (local, _) = local; // error 4 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(14, 22), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionAssignmentToRefMethods() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + (M(), M()) = local; // error + } + public static void Main() => throw null; + public ref Span M() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (9,22): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (M(), M()) = local; // error + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(9, 22), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionAssignmentWithRefExtension() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + (global, global) = global; + } + public static void Main() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(ref this Span self, out Span x, out Span y) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (8,9): error CS1510: A ref or out value must be an assignable variable + // (global, global) = global; + Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9), + // (8,28): error CS8129: No suitable Deconstruct instance or extension method was found for type 'Span', with 2 out parameters and a void return type. + // (global, global) = global; + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "global").WithArguments("System.Span", "2").WithLocation(8, 28), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionAssignmentWithRefReadonlyExtension() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + (global, global) = global; + (global, global) = local; // error + } + public static void Main() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(ref readonly this Span self, out Span x, out Span y) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (10,28): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, global) = local; // error + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(10, 28), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionAssignmentWithReturnValue() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + var t = ((global, global) = global); // error + } + public static void Main() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) => throw null; +} +namespace System +{ + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + } +}"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (8,19): error CS0306: The type 'Span' may not be used as a type argument + // var t = ((global, global) = global); // error + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(8, 19), + // (8,27): error CS0306: The type 'Span' may not be used as a type argument + // var t = ((global, global) = global); // error + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(8, 27), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionAssignmentOfTuple() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + string s; + C c; + + (global, global) = (local, local); // error 1 + + (global, s) = (local, """"); // error 2 + (global, s) = (local, null); // error 3 + + (local, s) = (global, """"); // error 4 + (local, s) = (global, null); // error 5 + + (c, s) = (local, """"); // error 6 + (c, s) = (local, null); + } + public static void Main() => throw null; + public static implicit operator C(Span s) => throw null; +} +namespace System +{ + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + } +} +"; + var compilation = CreateCompilationWithMscorlibAndSpan(text); + compilation.VerifyDiagnostics( + // (12,29): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 29), + // (12,36): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 36), + // (14,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, ""); // error 2 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(14, 24), + // (15,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, null); // error 3 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(15, 24), + // (17,23): error CS0306: The type 'Span' may not be used as a type argument + // (local, s) = (global, ""); // error 4 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(17, 23), + // (18,23): error CS0306: The type 'Span' may not be used as a type argument + // (local, s) = (global, null); // error 5 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(18, 23), + // (20,19): error CS0306: The type 'Span' may not be used as a type argument + // (c, s) = (local, ""); // error 6 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(20, 19) + ); + + // Check the Type and ConvertedType of tuples on the right-hand-side + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + + var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(3); + Assert.Equal(@"(local, """")", tuple2.ToString()); + Assert.Equal(@"(global, s) = (local, """")", tuple2.Parent.ToString()); + Assert.Equal("(System.Span local, string)", model.GetTypeInfo(tuple2).Type.ToString()); + Assert.Equal("(System.Span, string)", model.GetTypeInfo(tuple2).ConvertedType.ToString()); + + var tuple3 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(5); + Assert.Equal(@"(local, null)", tuple3.ToString()); + Assert.Equal(@"(global, s) = (local, null)", tuple3.Parent.ToString()); + Assert.Null(model.GetTypeInfo(tuple3).Type); + Assert.Equal("(System.Span, string)", model.GetTypeInfo(tuple3).ConvertedType.ToString()); + + var tuple6 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(11); + Assert.Equal(@"(local, """")", tuple6.ToString()); + Assert.Equal(@"(c, s) = (local, """")", tuple6.Parent.ToString()); + Assert.Equal("(System.Span local, string)", model.GetTypeInfo(tuple6).Type.ToString()); + Assert.Equal("(C, string)", model.GetTypeInfo(tuple6).ConvertedType.ToString()); + + var tuple7 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(13); + Assert.Equal("(local, null)", tuple7.ToString()); + Assert.Equal("(c, s) = (local, null)", tuple7.Parent.ToString()); + Assert.Null(model.GetTypeInfo(tuple7).Type); + Assert.Equal("(C, string)", model.GetTypeInfo(tuple7).ConvertedType.ToString()); + } + + [Fact] + public void DeconstructionAssignmentOfTuple_WithoutValueTuple() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + string s; + C c; + + (global, global) = (local, local); // error 1 + + (global, s) = (local, """"); // error 2 + (global, s) = (local, null); // error 3 + + (local, s) = (global, """"); // error 4 + (local, s) = (global, null); // error 5 + + (c, s) = (local, """"); // error 6 + (c, s) = (local, null); // error 7 + } + public static void Main() => throw null; + public static implicit operator C(Span s) => throw null; +} +"; + var compilation = CreateCompilationWithMscorlibAndSpan(text); + compilation.VerifyDiagnostics( + // (12,28): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(local, local)").WithArguments("System.ValueTuple`2").WithLocation(12, 28), + // (12,29): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 29), + // (12,36): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 36), + // (14,23): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (global, s) = (local, ""); // error 2 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, @"(local, """")").WithArguments("System.ValueTuple`2").WithLocation(14, 23), + // (14,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, ""); // error 2 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(14, 24), + // (15,23): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (global, s) = (local, null); // error 3 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(local, null)").WithArguments("System.ValueTuple`2").WithLocation(15, 23), + // (17,22): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (local, s) = (global, ""); // error 4 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, @"(global, """")").WithArguments("System.ValueTuple`2").WithLocation(17, 22), + // (17,23): error CS0306: The type 'Span' may not be used as a type argument + // (local, s) = (global, ""); // error 4 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(17, 23), + // (18,22): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (local, s) = (global, null); // error 5 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(global, null)").WithArguments("System.ValueTuple`2").WithLocation(18, 22), + // (20,18): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (c, s) = (local, ""); // error 6 + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, @"(local, """")").WithArguments("System.ValueTuple`2").WithLocation(20, 18), + // (20,19): error CS0306: The type 'Span' may not be used as a type argument + // (c, s) = (local, ""); // error 6 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(20, 19), + // (21,18): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (c, s) = (local, null); + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(local, null)").WithArguments("System.ValueTuple`2").WithLocation(21, 18) + ); + + // Check the Type and ConvertedType of tuples on the right-hand-side + var tree = compilation.SyntaxTrees[0]; + var model = compilation.GetSemanticModel(tree); + + var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(3); + Assert.Equal(@"(local, """")", tuple2.ToString()); + Assert.Equal(@"(global, s) = (local, """")", tuple2.Parent.ToString()); + Assert.Equal("(System.Span local, string)", model.GetTypeInfo(tuple2).Type.ToString()); + Assert.Equal("(System.Span, string)", model.GetTypeInfo(tuple2).ConvertedType.ToString()); + + var tuple3 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(5); + Assert.Equal(@"(local, null)", tuple3.ToString()); + Assert.Equal(@"(global, s) = (local, null)", tuple3.Parent.ToString()); + Assert.Null(model.GetTypeInfo(tuple3).Type); + Assert.Equal("(System.Span, string)", model.GetTypeInfo(tuple3).ConvertedType.ToString()); + + var tuple6 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(11); + Assert.Equal(@"(local, """")", tuple6.ToString()); + Assert.Equal(@"(c, s) = (local, """")", tuple6.Parent.ToString()); + Assert.Equal("(System.Span local, string)", model.GetTypeInfo(tuple6).Type.ToString()); + Assert.Equal("(C, string)", model.GetTypeInfo(tuple6).ConvertedType.ToString()); + + var tuple7 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(13); + Assert.Equal("(local, null)", tuple7.ToString()); + Assert.Equal("(c, s) = (local, null)", tuple7.Parent.ToString()); + Assert.Null(model.GetTypeInfo(tuple7).Type); + Assert.Equal("(C, string)", model.GetTypeInfo(tuple7).ConvertedType.ToString()); + } + + [Fact] + public void DeconstructionAssignmentOfRefLikeTuple() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + String s; + C c; + + (global, global) = (local, local); // error 1 + + (global, s) = (local, """"); // error 2 + (global, s) = (local, null); // error 3 + + (local, s) = (global, """"); // error 4 + (local, s) = (global, null); // error 5 + + (c, s) = (local, """"); // error 6 + (c, s) = (local, null); + } + public static void Main() => throw null; + public static implicit operator C(Span s) => throw null; +} +namespace System +{ + // Note: there is no way to make a ValueTuple type that will hold Spans and still be recognized as well-known type for tuples + public ref struct ValueTuple + { + public T1 Item1; + public T2 Item2; + + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + } +} +"; + var compilation = CreateCompilationWithMscorlibAndSpan(text); + compilation.VerifyDiagnostics( + // (12,29): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 29), + // (12,36): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 36), + // (12,29): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, global) = (local, local); // error 1 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(12, 29), + // (14,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, ""); // error 2 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(14, 24), + // (14,24): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, s) = (local, ""); // error 2 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(14, 24), + // (15,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, null); // error 3 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(15, 24), + // (15,24): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (global, s) = (local, null); // error 3 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(15, 24), + // (17,23): error CS0306: The type 'Span' may not be used as a type argument + // (local, s) = (global, ""); // error 4 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(17, 23), + // (18,23): error CS0306: The type 'Span' may not be used as a type argument + // (local, s) = (global, null); // error 5 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span").WithLocation(18, 23), + // (20,19): error CS0306: The type 'Span' may not be used as a type argument + // (c, s) = (local, ""); // error 6 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(20, 19) + ); + } + + [Fact] + public void DeconstructionAssignmentToOuter() + { + var text = @" +using System; + +public class C +{ + public void M() + { + Span outer = stackalloc int[10]; + + { + Span local = stackalloc int[10]; // both stackallocs have the same escape scope + (outer, outer) = outer; + (outer, outer) = local; + (outer, local) = local; + (local, local) = local; + } + } + public static void Main() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) => throw null; +} +"; + + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void DeconstructionDeclaration() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + var (local1, local2) = local; + global = local1; // error 1 + global = local2; // error 2 + + var (local3, local4) = global; + global = local3; + global = local4; + } + public static void Main() => throw null; +} +public static class Extensions +{ + public static void Deconstruct(this Span self, out Span x, out Span y) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (10,18): error CS8352: Cannot use local 'local1' in this context because it may expose referenced variables outside of their declaration scope + // global = local1; // error 1 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local1").WithArguments("local1").WithLocation(10, 18), + // (11,18): error CS8352: Cannot use local 'local2' in this context because it may expose referenced variables outside of their declaration scope + // global = local2; // error 2 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local2").WithArguments("local2").WithLocation(11, 18), + // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' + Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) + ); + } + + [Fact] + public void RefLikeForeach() + { + var text = @" +using System; + +public class C +{ + public void M(ref S global) + { + S localCollection = stackalloc int[10]; + foreach (var local in localCollection) + { + global = local; // error + } + } + public static void Main() => throw null; +} + +public ref struct S +{ + public S GetEnumerator() => throw null; + public bool MoveNext() => throw null; + public S Current => throw null; + public static implicit operator S(Span s) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (11,22): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // global = local; // error + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(11, 22) + ); + } + + [Fact] + public void RefLikeDeconstructionForeach() + { + var text = @" +using System; + +public class C +{ + public void M(ref S global) + { + S localCollection = stackalloc int[10]; + foreach (var (local1, local2) in localCollection) + { + global = local1; // error 1 + global = local2; // error 2 + } + } + public static void Main() => throw null; +} + +public ref struct S +{ + public S GetEnumerator() => throw null; + public bool MoveNext() => throw null; + public S Current => throw null; + public static implicit operator S(Span s) => throw null; + public void Deconstruct(out S s1, out S s2) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (11,22): error CS8352: Cannot use local 'local1' in this context because it may expose referenced variables outside of their declaration scope + // global = local1; // error 1 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local1").WithArguments("local1").WithLocation(11, 22), + // (12,22): error CS8352: Cannot use local 'local2' in this context because it may expose referenced variables outside of their declaration scope + // global = local2; // error 2 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local2").WithArguments("local2").WithLocation(12, 22) + ); + } + + [Fact] + [WorkItem(22361, "https://github.com/dotnet/roslyn/issues/")] + public void RefLikeOutVarFromLocal() + { + var text = @" +using System; + +public class C +{ + public void M(ref S global) + { + S local1 = stackalloc int[10]; + local1.M(out S local2); // we'd want this to succeed, but determine the safe-to-escape scope for local2 based on the invocation that declared it + local1 = local2; // then this would be allowed + global = local2; // and this would fail + } + public static void Main() => throw null; +} + +public ref struct S +{ + public static implicit operator S(Span s) => throw null; + public void M(out S s) => throw null; +} +"; + // Tracking issue: https://github.com/dotnet/roslyn/issues/22361 + + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (9,9): error CS8352: Cannot use local 'local1' in this context because it may expose referenced variables outside of their declaration scope + // local1.M(out S local2); + Diagnostic(ErrorCode.ERR_EscapeLocal, "local1").WithArguments("local1").WithLocation(9, 9) + ); + } + + [Fact] + public void RefLikeOutVarFromGlobal() + { + var text = @" +using System; + +public class C +{ + public void M(ref S global) + { + global.M(out S local2); + global = local2; + } + public static void Main() => throw null; +} + +public ref struct S +{ + public static implicit operator S(Span s) => throw null; + public void M(out S s) => throw null; +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs index 551a999bf7ed5..40cae5c33dfa5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs @@ -4953,24 +4953,77 @@ static void Main() } [Fact] - public void TupleLiteralElement004() + public void TupleLiteralElement004_WithoutValueTuple() { var source = @" +class C +{ + static void Main() + { + (short X, string Y) = (Alice: 1, Bob: null); + } +} +"; -using System; + var compilation = CreateStandardCompilation(source); + compilation.VerifyDiagnostics( + // (6,31): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies + // (short X, string Y) = (Alice: 1, Bob: null); + Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(Alice: 1, Bob: null)").WithArguments("System.ValueTuple`2").WithLocation(6, 31), + // (6,32): warning CS8123: The tuple element name 'Alice' is ignored because a different name or no name is specified by the target type '(short, string)'. + // (short X, string Y) = (Alice: 1, Bob: null); + Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Alice: 1").WithArguments("Alice", "(short, string)").WithLocation(6, 32), + // (6,42): warning CS8123: The tuple element name 'Bob' is ignored because a different name or no name is specified by the target type '(short, string)'. + // (short X, string Y) = (Alice: 1, Bob: null); + Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Bob: null").WithArguments("Bob", "(short, string)").WithLocation(6, 42) + ); + var tree = compilation.SyntaxTrees[0]; + var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument)); + var model = compilation.GetSemanticModel(tree); + var element = (FieldSymbol)model.GetDeclaredSymbol(decl); + Assert.Equal(element.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), "(short Alice, string Bob).Bob"); + Assert.Equal(element.DeclaringSyntaxReferences.Single().GetSyntax().ToString(), "Bob"); + Assert.Equal(element.Locations.Single().IsInSource, true); + } + [Fact] + public void TupleLiteralElement004() + { + var source = +@" class C { - static void Main() - { + static void Main() + { (short X, string Y) = (Alice: 1, Bob: null); } } +namespace System +{ + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + } +} "; var compilation = CreateStandardCompilation(source); + compilation.VerifyDiagnostics( + // (6,32): warning CS8123: The tuple element name 'Alice' is ignored because a different name or no name is specified by the target type '(short, string)'. + // (short X, string Y) = (Alice: 1, Bob: null); + Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Alice: 1").WithArguments("Alice", "(short, string)").WithLocation(6, 32), + // (6,42): warning CS8123: The tuple element name 'Bob' is ignored because a different name or no name is specified by the target type '(short, string)'. + // (short X, string Y) = (Alice: 1, Bob: null); + Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Bob: null").WithArguments("Bob", "(short, string)").WithLocation(6, 42) + ); var tree = compilation.SyntaxTrees[0]; var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument)); var model = compilation.GetSemanticModel(tree);