From 5943daf607d5ed970fcb97754341c3a2cf901f4f Mon Sep 17 00:00:00 2001 From: jcouv Date: Mon, 25 Sep 2017 20:55:18 -0700 Subject: [PATCH 1/6] Escape rules for deconstruction and foreach variables --- .../Portable/Binder/Binder.ValueChecks.cs | 40 +- .../Portable/Binder/Binder_Deconstruct.cs | 30 +- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../Portable/Binder/ForEachLoopBinder.cs | 20 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 1 + .../OutDeconstructVarPendingInference.cs | 8 +- .../Generated/BoundNodes.xml.Generated.cs | 17 +- .../Symbols/Source/SourceLocalSymbol.cs | 2 +- .../Semantic/Semantics/RefEscapingTests.cs | 410 ++++++++++++++++++ 9 files changed, 500 insertions(+), 30 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 174cd7d4b74ea..0be8d37152d63 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. /// @@ -1990,6 +2011,8 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin case BoundKind.DefaultExpression: case BoundKind.Parameter: case BoundKind.ThisReference: + case BoundKind.TupleLiteral: + case BoundKind.ConvertedTupleLiteral: // always returnable return Binder.ExternalScope; @@ -2004,6 +2027,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; @@ -2237,6 +2263,8 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint case BoundKind.DefaultExpression: case BoundKind.Parameter: case BoundKind.ThisReference: + case BoundKind.TupleLiteral: + case BoundKind.ConvertedTupleLiteral: // always returnable return true; @@ -2249,6 +2277,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 +2500,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 +2576,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint // case BoundKind.DeclarationPattern: // case BoundKind.ConstantPattern: // case BoundKind.WildcardPattern: -// case BoundKind.DeconstructValuePlaceholder: #endregion diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 54f1486efd020..e59f2daef5f8d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -113,10 +113,12 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( 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 +141,14 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( checkedVariables, out conversion); - FailRemainingInferences(checkedVariables, diagnostics); + FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !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, @@ -248,7 +253,7 @@ private bool MakeDeconstructionConversion( 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 +350,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 +361,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; @@ -672,7 +686,7 @@ private BoundBadExpression MissingDeconstruct(BoundExpression receiver, SyntaxNo 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 void CoerceArguments( 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..1be93b5da351f 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -168,7 +168,8 @@ 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")); + uint collectionEscape = GetValEscape(collectionExpr, this.LocalScopeDepth); + var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, inferredType ?? CreateErrorType("var")); DeclarationExpressionSyntax declaration = null; ExpressionSyntax expression = null; BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction( @@ -202,6 +203,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 +232,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 +247,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 +289,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 +355,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..c89c690d73cd9 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -9,17 +9,17 @@ 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); + 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..8b76005008d4e 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2386,5 +2386,415 @@ .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; + } + 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; + 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; + } + 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; + 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); + } + 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); + 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); + 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]; + (global, global) = (local, local); + } + public static void Main() => 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( + // (9,29): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 29), + // (9,36): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 36) + ); + } + + [Fact] + public void DeconstructionAssignmentOfRefLikeTuple() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + (global, global) = (local, local); + } + public static void Main() => 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; + } + } +} +"; + CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + // (9,29): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 29), + // (9,36): error CS0306: The type 'Span' may not be used as a type argument + // (global, global) = (local, local); + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 36) + ); + } + + [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; + global = local2; + + 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; + 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; + 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; + } + } + 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; + 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) + ); + } } } From 462c0e2fa1a14413c6fba0a126df0871b9939529 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 26 Sep 2017 15:10:55 -0700 Subject: [PATCH 2/6] Tweaks --- src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs | 6 ++++-- .../Portable/BoundTree/OutDeconstructVarPendingInference.cs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 1be93b5da351f..c6142b9f99100 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -168,8 +168,10 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType); ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable; - uint collectionEscape = GetValEscape(collectionExpr, this.LocalScopeDepth); - var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, 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( diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs index c89c690d73cd9..8df2244782a60 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -13,6 +13,7 @@ public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, Binder { Debug.Assert(Placeholder is null); + // 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; } From 952792adff7d89bfd2c365752820691e24c4d490 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 26 Sep 2017 16:41:29 -0700 Subject: [PATCH 3/6] Tweaks --- .../Semantic/Semantics/RefEscapingTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 8b76005008d4e..6b16317388525 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2796,5 +2796,64 @@ public ref struct S 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(); + } } } From a135d99bb50e3a32f8994675357a9cb02a29d66c Mon Sep 17 00:00:00 2001 From: jcouv Date: Tue, 26 Sep 2017 21:13:53 -0700 Subject: [PATCH 4/6] Report constraint errors on deconstruction of typeless tuple --- .../Portable/Binder/Binder_Deconstruct.cs | 27 +++--- .../Semantic/Semantics/DeconstructionTests.cs | 5 +- .../Semantic/Semantics/RefEscapingTests.cs | 94 ++++++++++++++----- .../SemanticModelGetDeclaredSymbolAPITests.cs | 52 +++++++++- 4 files changed, 136 insertions(+), 42 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index e59f2daef5f8d..6ea9c6c769f4c 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,18 @@ internal BoundDeconstructionAssignmentOperator BindDeconstruction( 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); + if (!deconstructionDiagnostics.HasAnyErrors()) + { + 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; } @@ -143,7 +147,7 @@ private BoundDeconstructionAssignmentOperator BindDeconstructionAssignment( 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); @@ -453,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]; @@ -493,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, diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs index e8dc27f904426..fca8dbe0ba300 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs @@ -4596,10 +4596,7 @@ static void Main() Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(5, 17), // (5,31): error CS8210: A tuple may not contain a value of type 'void'. // (int x, void y) = (1, Main()); - Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31), - // (5,17): error CS0029: Cannot implicitly convert type 'void' to 'void' - // (int x, void y) = (1, Main()); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "void y").WithArguments("void", "void").WithLocation(5, 17) + Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31) ); var main = comp.GetMember("C.Main"); var tree = comp.SyntaxTrees[0]; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 6b16317388525..df0d9f64a6a8b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2447,7 +2447,7 @@ public class C public void M(ref Span global) { Span local = stackalloc int[10]; - (M(), M()) = local; + (M(), M()) = local; // error } public static void Main() => throw null; public ref Span M() => throw null; @@ -2459,7 +2459,7 @@ public static class Extensions "; 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; + // (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) @@ -2509,7 +2509,7 @@ public void M(ref Span global) { Span local = stackalloc int[10]; (global, global) = global; - (global, global) = local; + (global, global) = local; // error } public static void Main() => throw null; } @@ -2520,7 +2520,7 @@ public static class Extensions "; 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; + // (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) @@ -2537,7 +2537,7 @@ public class C { public void M(ref Span global) { - var t = ((global, global) = global); + var t = ((global, global) = global); // error } public static void Main() => throw null; } @@ -2561,10 +2561,10 @@ public ValueTuple(T1 item1, T2 item2) }"; CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( // (8,19): error CS0306: The type 'Span' may not be used as a type argument - // var t = ((global, global) = global); + // 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); + // 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) @@ -2582,7 +2582,9 @@ public class C public void M(ref Span global) { Span local = stackalloc int[10]; - (global, global) = (local, local); + string s; + (global, global) = (local, local); // error 1 + (global, s) = (local, """"); // error 2 } public static void Main() => throw null; } @@ -2602,12 +2604,58 @@ public ValueTuple(T1 item1, T2 item2) } "; CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( - // (9,29): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 29), - // (9,36): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 36) + // (10,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(10, 29), + // (10,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(10, 36), + // (11,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(11, 24) + ); + } + + [Fact] + public void DeconstructionAssignmentOfTypelessTuple() + { + var text = @" +using System; + +public class C +{ + public void M(ref Span global) + { + Span local = stackalloc int[10]; + Span local2 = stackalloc int[10]; + string s; + (global, s) = (local, null); // error 1 + (local2, s) = (local, null); // error 2 + } + public static void Main() => 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( + // (11,24): error CS0306: The type 'Span' may not be used as a type argument + // (global, s) = (local, null); // error 1 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(11, 24), + // (12,24): error CS0306: The type 'Span' may not be used as a type argument + // (local2, s) = (local, null); // error 2 + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 24) ); } @@ -2622,7 +2670,7 @@ public class C public void M(ref Span global) { Span local = stackalloc int[10]; - (global, global) = (local, local); + (global, global) = (local, local); // error } public static void Main() => throw null; } @@ -2644,10 +2692,10 @@ public ValueTuple(T1 item1, T2 item2) "; CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( // (9,29): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); + // (global, global) = (local, local); // error Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 29), // (9,36): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); + // (global, global) = (local, local); // error Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 36) ); } @@ -2698,8 +2746,8 @@ public void M(ref Span global) { Span local = stackalloc int[10]; var (local1, local2) = local; - global = local1; - global = local2; + global = local1; // error 1 + global = local2; // error 2 var (local3, local4) = global; global = local3; @@ -2714,10 +2762,10 @@ public static class Extensions "; 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; + // 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; + // 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) @@ -2737,7 +2785,7 @@ public void M(ref S global) S localCollection = stackalloc int[10]; foreach (var local in localCollection) { - global = local; + global = local; // error } } public static void Main() => throw null; @@ -2753,7 +2801,7 @@ public ref struct S "; 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; + // global = local; // error Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(11, 22) ); } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs index 551a999bf7ed5..e84cfc8193c18 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs @@ -4953,24 +4953,68 @@ 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) + ); + var tree = compilation.SyntaxTrees[0]; + var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument)); + var model = compilation.GetSemanticModel(tree); + Assert.Null(model.GetDeclaredSymbol(decl)); + } + [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); From 5ec929b8f1ab09704dfce6eb74c6078a0d32d02c Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 27 Sep 2017 16:27:51 -0700 Subject: [PATCH 5/6] Pre-emptively implement val escape logic for tuples --- .../Portable/Binder/Binder.ValueChecks.cs | 44 +++++- .../Semantic/Semantics/RefEscapingTests.cs | 128 ++++++++++-------- 2 files changed, 114 insertions(+), 58 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 0be8d37152d63..2b64d111af69a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -2011,11 +2011,17 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin case BoundKind.DefaultExpression: case BoundKind.Parameter: case BoundKind.ThisReference: - case BoundKind.TupleLiteral: - case BoundKind.ConvertedTupleLiteral: // 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 @@ -2192,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; @@ -2263,11 +2280,17 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint case BoundKind.DefaultExpression: case BoundKind.Parameter: case BoundKind.ThisReference: - case BoundKind.TupleLiteral: - case BoundKind.ConvertedTupleLiteral: // 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 @@ -2611,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/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index df0d9f64a6a8b..862c75613aef6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2583,10 +2583,21 @@ 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 { @@ -2604,20 +2615,32 @@ public ValueTuple(T1 item1, T2 item2) } "; CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( - // (10,29): error CS0306: The type 'Span' may not be used as a type argument + // (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(10, 29), - // (10,36): error CS0306: The type 'Span' may not be used as a type argument + 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(10, 36), - // (11,24): error CS0306: The type 'Span' may not be used as a type argument + 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(11, 24) + 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) ); } [Fact] - public void DeconstructionAssignmentOfTypelessTuple() + public void DeconstructionAssignmentOfRefLikeTuple() { var text = @" using System; @@ -2627,52 +2650,22 @@ public class C public void M(ref Span global) { Span local = stackalloc int[10]; - Span local2 = stackalloc int[10]; - string s; - (global, s) = (local, null); // error 1 - (local2, s) = (local, null); // error 2 - } - public static void Main() => throw null; -} -namespace System -{ - public struct ValueTuple - { - public T1 Item1; - public T2 Item2; + String s; + C c; - public ValueTuple(T1 item1, T2 item2) - { - this.Item1 = item1; - this.Item2 = item2; - } - } -} -"; - CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( - // (11,24): error CS0306: The type 'Span' may not be used as a type argument - // (global, s) = (local, null); // error 1 - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(11, 24), - // (12,24): error CS0306: The type 'Span' may not be used as a type argument - // (local2, s) = (local, null); // error 2 - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(12, 24) - ); - } + (global, global) = (local, local); // error 1 - [Fact] - public void DeconstructionAssignmentOfRefLikeTuple() - { - var text = @" -using System; + (global, s) = (local, """"); // error 2 + (global, s) = (local, null); // error 3 -public class C -{ - public void M(ref Span global) - { - Span local = stackalloc int[10]; - (global, global) = (local, local); // error + (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 { @@ -2691,12 +2684,39 @@ public ValueTuple(T1 item1, T2 item2) } "; CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( - // (9,29): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); // error - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 29), - // (9,36): error CS0306: The type 'Span' may not be used as a type argument - // (global, global) = (local, local); // error - Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(9, 36) + // (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), + // (20,19): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope + // (c, s) = (local, ""); // error 6 + Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(20, 19) ); } From 68d0b4973b4528632f92f792a459aee48e21b9c0 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 27 Sep 2017 20:04:07 -0700 Subject: [PATCH 6/6] Always make the merged tuple type for tuple deconstructions --- .../Portable/Binder/Binder_Deconstruct.cs | 10 +- .../Semantic/Semantics/DeconstructionTests.cs | 5 +- .../Semantic/Semantics/RefEscapingTests.cs | 137 +++++++++++++++++- .../SemanticModelGetDeclaredSymbolAPITests.cs | 13 +- 4 files changed, 151 insertions(+), 14 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 6ea9c6c769f4c..1626b4610e51a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -96,10 +96,7 @@ internal BoundDeconstructionAssignmentOperator BindDeconstruction( var deconstructionDiagnostics = new DiagnosticBag(); BoundExpression boundRight = rightPlaceholder ?? BindValue(right, deconstructionDiagnostics, BindValueKind.RValue); - if (!deconstructionDiagnostics.HasAnyErrors()) - { - boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics); - } + boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics); bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left); var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, deconstructionDiagnostics); @@ -206,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); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs index fca8dbe0ba300..e8dc27f904426 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs @@ -4596,7 +4596,10 @@ static void Main() Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(5, 17), // (5,31): error CS8210: A tuple may not contain a value of type 'void'. // (int x, void y) = (1, Main()); - Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31) + Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31), + // (5,17): error CS0029: Cannot implicitly convert type 'void' to 'void' + // (int x, void y) = (1, Main()); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "void y").WithArguments("void", "void").WithLocation(5, 17) ); var main = comp.GetMember("C.Main"); var tree = comp.SyntaxTrees[0]; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 862c75613aef6..412d1b25cf5dc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2614,7 +2614,8 @@ public ValueTuple(T1 item1, T2 item2) } } "; - CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + 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), @@ -2637,6 +2638,132 @@ public ValueTuple(T1 item1, T2 item2) // (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] @@ -2683,7 +2810,8 @@ public ValueTuple(T1 item1, T2 item2) } } "; - CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics( + 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), @@ -2713,10 +2841,7 @@ public ValueTuple(T1 item1, T2 item2) 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), - // (20,19): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope - // (c, s) = (local, ""); // error 6 - Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(20, 19) + Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span").WithLocation(20, 19) ); } diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs index e84cfc8193c18..40cae5c33dfa5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/SemanticModelGetDeclaredSymbolAPITests.cs @@ -4970,12 +4970,21 @@ static void Main() 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) + 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); - Assert.Null(model.GetDeclaredSymbol(decl)); + 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]