diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 1e5f56dbcae9c..86ee7db2c0368 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1724,96 +1724,275 @@ private BoundExpression BindAssignment(AssignmentExpressionSyntax node, Diagnost /// private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax node, DiagnosticBag diagnostics) { + // receiver for first Deconstruct step + var boundRHS = BindValue(node.Right, diagnostics, BindValueKind.RValue); + SeparatedSyntaxList arguments = ((TupleExpressionSyntax)node.Left).Arguments; + ImmutableArray checkedVariables = BindDeconstructionVariables(arguments, diagnostics); - // receiver for Deconstruct - var boundRHS = BindValue(node.Right, diagnostics, BindValueKind.RValue); + if ((object)boundRHS.Type == null) + { + if (boundRHS.Kind == BoundKind.TupleLiteral) + { + // tuple literal without type such as `(null, null)`, let's fix it up by peeking at the LHS + TypeSymbol lhsAsTuple = MakeTupleTypeFromDeconstructionLHS(checkedVariables, diagnostics, Compilation); + boundRHS = GenerateConversionForAssignment(lhsAsTuple, boundRHS, diagnostics); + } + else + { + // expression without type such as `null` + Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node); + return BadExpression(node, FlattenDeconstructVariables(checkedVariables).Concat(boundRHS).ToArray()); + } + } - int numElements = arguments.Count; - Debug.Assert(numElements >= 2); // this should not have parsed as a tuple. + var deconstructionSteps = ArrayBuilder.GetInstance(1); + var assignmentSteps = ArrayBuilder.GetInstance(1); + try + { + bool hasErrors = !DeconstructIntoSteps(new BoundDeconstructValuePlaceholder(node.Right, boundRHS.Type), node, diagnostics, checkedVariables, deconstructionSteps, assignmentSteps); - // bind the variables and check they can be assigned to - var checkedVariablesBuilder = ArrayBuilder.GetInstance(numElements); - foreach (var argument in arguments) + var deconstructions = deconstructionSteps.ToImmutable(); + var assignments = assignmentSteps.ToImmutable(); + + TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); + return new BoundDeconstructionAssignmentOperator(node, FlattenDeconstructVariables(checkedVariables), boundRHS, deconstructions, assignments, voidType, hasErrors: hasErrors); + } + finally + { + deconstructionSteps.Free(); + assignmentSteps.Free(); + } + } + + /// + /// Whether the target is a tuple or a type that requires Deconstruction, this will generate and stack appropriate deconstruction and assignment steps. + /// Note that the variables may either be plain or nested variables. + /// Returns false if there was an error. + /// + private bool DeconstructIntoSteps( + BoundDeconstructValuePlaceholder targetPlaceholder, + AssignmentExpressionSyntax syntax, + DiagnosticBag diagnostics, + ImmutableArray variables, + ArrayBuilder deconstructionSteps, + ArrayBuilder assignmentSteps) + { + Debug.Assert(targetPlaceholder.Type != null); + + BoundDeconstructionDeconstructStep step; + + if (targetPlaceholder.Type.IsTupleType) + { + // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())` + step = MakeTupleDeconstructStep(targetPlaceholder, syntax, diagnostics, variables, deconstructionSteps, assignmentSteps); + } + else { - var boundVariable = BindExpression(argument.Expression, diagnostics, invoked: false, indexed: false); - var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignment, diagnostics); + step = MakeNonTupleDeconstructStep(targetPlaceholder, syntax, diagnostics, variables, deconstructionSteps, assignmentSteps); + } - checkedVariablesBuilder.Add(checkedVariable); + if (step == null) + { + return false; } - var checkedVariables = checkedVariablesBuilder.ToImmutableAndFree(); + deconstructionSteps.Add(step); + + // outputs will either need a conversion step and assignment step, or if they are nested variables, they will need further deconstruction + return DeconstructOrAssignOutputs(step, variables, syntax, diagnostics, deconstructionSteps, assignmentSteps); + } - // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())` - if (boundRHS.Kind == BoundKind.TupleLiteral || ((object)boundRHS.Type != null && boundRHS.Type.IsTupleType)) + /// + /// This will generate and stack appropriate deconstruction and assignment steps for a tuple type. + /// The produced deconstruction step has no Deconstruct method since the tuple already has distinct elements. + /// + static private BoundDeconstructionDeconstructStep MakeTupleDeconstructStep( + BoundDeconstructValuePlaceholder targetPlaceholder, + AssignmentExpressionSyntax syntax, + DiagnosticBag diagnostics, + ImmutableArray variables, + ArrayBuilder deconstructionSteps, + ArrayBuilder assignmentSteps) + { + Debug.Assert(targetPlaceholder.Type.IsTupleType); + + var tupleTypes = targetPlaceholder.Type.TupleElementTypes; + if (variables.Length != tupleTypes.Length) { - return BindDeconstructWithTuple(node, diagnostics, boundRHS, checkedVariables); + Error(diagnostics, ErrorCode.ERR_DeconstructWrongCardinality, syntax, tupleTypes.Length, variables.Length); + return null; } - // expression without type such as `null` - if (boundRHS.Type == null) + return new BoundDeconstructionDeconstructStep(syntax, null, targetPlaceholder, tupleTypes.SelectAsArray((t, s) => new BoundDeconstructValuePlaceholder(s, t), syntax)); + } + + /// + /// This will generate and stack appropriate deconstruction and assignment steps for a non-tuple type. + /// Returns null if there was an error (if a suitable Deconstruct method was not found). + /// + static private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep( + BoundDeconstructValuePlaceholder targetPlaceholder, + AssignmentExpressionSyntax syntax, + DiagnosticBag diagnostics, + ImmutableArray variables, + ArrayBuilder deconstructionSteps, + ArrayBuilder assignmentSteps) + { + // symbol and parameters for Deconstruct + MethodSymbol deconstructMethod = FindDeconstruct(variables.Length, targetPlaceholder, syntax, diagnostics); + + if ((object)deconstructMethod == null) { - Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node); - return BadExpression(node, checkedVariables.Concat(boundRHS).ToArray()); + return null; } - return BindDeconstructWithDeconstruct(node, diagnostics, boundRHS, checkedVariables); + return new BoundDeconstructionDeconstructStep(syntax, deconstructMethod, targetPlaceholder, deconstructMethod.Parameters.SelectAsArray((p, s) => new BoundDeconstructValuePlaceholder(s, p.Type), syntax)); } - private BoundExpression BindDeconstructWithTuple(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression boundRHS, ImmutableArray checkedVariables) + private class DeconstructionVariable { - // make a conversion for the tuple based on the LHS information (this also takes care of tuple literals without a natural type) - var lhsTypes = checkedVariables.SelectAsArray(v => v.Type); - TypeSymbol lhsAsTuple = TupleTypeSymbol.Create(locationOpt: null, elementTypes: lhsTypes, elementLocations: default(ImmutableArray), elementNames: default(ImmutableArray), compilation: Compilation, diagnostics: diagnostics); - var typedRHS = GenerateConversionForAssignment(lhsAsTuple, boundRHS, diagnostics); + public readonly BoundExpression Single; + public readonly ImmutableArray Nested; - ImmutableArray tupleTypes = typedRHS.Type.TupleElementTypes; + public DeconstructionVariable(BoundExpression variable) + { + Single = variable; + Nested = default(ImmutableArray); + } - // figure out the pairwise conversions - var assignments = checkedVariables.SelectAsArray((variable, index, types) => MakeAssignmentInfo(variable, types[index], node, diagnostics), tupleTypes); + public DeconstructionVariable(ImmutableArray variables) + { + Single = null; + Nested = variables; + } - TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); - return new BoundDeconstructionAssignmentOperator(node, checkedVariables, typedRHS, deconstructMemberOpt: null, assignments: assignments, type: voidType); + public bool IsNested => !Nested.IsDefault; } - private BoundExpression BindDeconstructWithDeconstruct(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression boundRHS, ImmutableArray checkedVariables) + /// + /// Takes the outputs from the previous deconstructionStep and depending on the structure of variables, will generate further deconstructions, or simply assignments. + /// Returns true for success, but false if has errors. + /// + private bool DeconstructOrAssignOutputs( + BoundDeconstructionDeconstructStep deconstructionStep, + ImmutableArray variables, + AssignmentExpressionSyntax syntax, + DiagnosticBag diagnostics, + ArrayBuilder deconstructionSteps, + ArrayBuilder assignmentSteps) { - // symbol and parameters for Deconstruct - DiagnosticBag bag = new DiagnosticBag(); - MethodSymbol deconstructMethod = FindDeconstruct(checkedVariables, boundRHS, node, bag); - if (!diagnostics.HasAnyErrors()) + bool hasErrors = false; + + for (int i = 0; i < variables.Length; i++) { - diagnostics.AddRange(bag); + var variable = variables[i]; + var valuePlaceholder = deconstructionStep.OutputPlaceholders[i]; + + if (variable.IsNested) + { + if (!DeconstructIntoSteps(valuePlaceholder, syntax, diagnostics, variable.Nested, deconstructionSteps, assignmentSteps)) + { + hasErrors = true; + } + } + else + { + var assignment = MakeAssignmentInfo(variable.Single, valuePlaceholder.Type, valuePlaceholder, syntax, diagnostics); + assignmentSteps.Add(assignment); + } } - if ((object)deconstructMethod == null) + return !hasErrors; + } + + /// + /// For cases where the RHS of a deconstruction-assignment has no type (TupleLiteral), we squint and look at the LHS as a tuple type to give the RHS a type. + /// + static private TypeSymbol MakeTupleTypeFromDeconstructionLHS(ImmutableArray topLevelCheckedVariables, DiagnosticBag diagnostics, CSharpCompilation compilation) + { + var typesBuilder = ArrayBuilder.GetInstance(topLevelCheckedVariables.Length); + foreach (var variable in topLevelCheckedVariables) { - return new BoundDeconstructionAssignmentOperator(node, checkedVariables, boundRHS, ErrorMethodSymbol.UnknownMethod, - ImmutableArray.Empty, - ErrorTypeSymbol.UnknownResultType, - hasErrors: true); + if (variable.IsNested) + { + typesBuilder.Add(MakeTupleTypeFromDeconstructionLHS(variable.Nested, diagnostics, compilation)); + } + else + { + typesBuilder.Add(variable.Single.Type); + } } - // figure out the pairwise conversions - var deconstructParameters = deconstructMethod.Parameters; - var assignments = checkedVariables.SelectAsArray((variable, index, parameters) => MakeAssignmentInfo(variable, parameters[index].Type, node, diagnostics), deconstructParameters); + return TupleTypeSymbol.Create(locationOpt: null, elementTypes: typesBuilder.ToImmutableAndFree(), elementLocations: default(ImmutableArray), elementNames: default(ImmutableArray), compilation: compilation, diagnostics: diagnostics); + } + + /// + /// Returns an array of variables, where some may be nested variables (BoundDeconstructionVariables). + /// Checks that all the variables are assignable to. + /// + private ImmutableArray BindDeconstructionVariables(SeparatedSyntaxList arguments, DiagnosticBag diagnostics) + { + int numElements = arguments.Count; + Debug.Assert(numElements >= 2); // this should not have parsed as a tuple. + + // bind the variables and check they can be assigned to + var checkedVariablesBuilder = ArrayBuilder.GetInstance(numElements); + + foreach (var argument in arguments) + { + if (argument.Expression.Kind() == SyntaxKind.TupleExpression) // nested tuple case + { + var nestedArguments = ((TupleExpressionSyntax)argument.Expression).Arguments; + checkedVariablesBuilder.Add(new DeconstructionVariable(BindDeconstructionVariables(nestedArguments, diagnostics))); + } + else + { + var boundVariable = BindExpression(argument.Expression, diagnostics, invoked: false, indexed: false); + var checkedVariable = CheckValue(boundVariable, BindValueKind.Assignment, diagnostics); + + checkedVariablesBuilder.Add(new DeconstructionVariable(checkedVariable)); + } + } - TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); - return new BoundDeconstructionAssignmentOperator(node, checkedVariables, boundRHS, deconstructMethod, assignments, voidType); + var checkedVariables = checkedVariablesBuilder.ToImmutableAndFree(); + return checkedVariables; } /// /// Figures out how to assign from sourceType into receivingVariable and bundles the information (leaving holes for the actual source and receiver) into an AssignmentInfo. /// - private BoundDeconstructionAssignmentOperator.AssignmentInfo MakeAssignmentInfo(BoundExpression receivingVariable, TypeSymbol sourceType, AssignmentExpressionSyntax node, DiagnosticBag diagnostics) + private BoundDeconstructionAssignmentStep MakeAssignmentInfo(BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder, AssignmentExpressionSyntax node, DiagnosticBag diagnostics) { - var leftPlaceholder = new BoundLValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true }; - var rightPlaceholder = new BoundRValuePlaceholder(node.Right, sourceType) { WasCompilerGenerated = true }; + var outputPlaceholder = new BoundDeconstructValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true }; // each assignment has a placeholder for a receiver and another for the source - BoundAssignmentOperator op = BindAssignment(node, leftPlaceholder, rightPlaceholder, diagnostics); + BoundAssignmentOperator op = BindAssignment(node, outputPlaceholder, inputPlaceholder, diagnostics); - return new BoundDeconstructionAssignmentOperator.AssignmentInfo() { Assignment = op, LValuePlaceholder = leftPlaceholder, RValuePlaceholder = rightPlaceholder }; + return new BoundDeconstructionAssignmentStep(node, op, inputPlaceholder, outputPlaceholder); + } + + static private ImmutableArray FlattenDeconstructVariables(ImmutableArray variables) + { + var builder = ArrayBuilder.GetInstance(variables.Length); + FlattenDeconstructVariables(variables, builder); + + return builder.ToImmutableAndFree(); + } + + static private void FlattenDeconstructVariables(ImmutableArray variables, ArrayBuilder builder) + { + foreach (var variable in variables) + { + if (variable.IsNested) + { + FlattenDeconstructVariables(variable.Nested, builder); + } + else + { + builder.Add(variable.Single); + } + } } /// @@ -1821,7 +2000,7 @@ private BoundDeconstructionAssignmentOperator.AssignmentInfo MakeAssignmentInfo( /// Returns true if the Deconstruct method is found. /// If so, it outputs the method. /// - private static MethodSymbol FindDeconstruct(ImmutableArray checkedVariables, BoundExpression boundRHS, SyntaxNode node, DiagnosticBag diagnostics) + private static MethodSymbol FindDeconstruct(int numCheckedVariables, BoundExpression boundRHS, SyntaxNode node, DiagnosticBag diagnostics) { // find symbol for Deconstruct member ImmutableArray candidates = boundRHS.Type.GetMembers("Deconstruct"); @@ -1853,9 +2032,9 @@ private static MethodSymbol FindDeconstruct(ImmutableArray chec return null; } - if (deconstructMethod.ParameterCount != checkedVariables.Length) + if (deconstructMethod.ParameterCount != numCheckedVariables) { - Error(diagnostics, ErrorCode.ERR_DeconstructWrongParams, boundRHS.Syntax, deconstructMethod, checkedVariables.Length); + Error(diagnostics, ErrorCode.ERR_DeconstructWrongParams, boundRHS.Syntax, deconstructMethod, numCheckedVariables); return null; } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs index 9ebbdc8f0d059..b0243a2ea198a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs @@ -278,16 +278,6 @@ public override Symbol ExpressionSymbol public ImmutableArray OriginalUserDefinedOperatorsOpt { get; } } - internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpression - { - internal class AssignmentInfo - { - public BoundAssignmentOperator Assignment; - public BoundLValuePlaceholder LValuePlaceholder; - public BoundRValuePlaceholder RValuePlaceholder; - } - } - internal partial class BoundCompoundAssignmentOperator { public override Symbol ExpressionSymbol diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 80a65cbf18e35..677404f63ca20 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -84,9 +84,7 @@ This node is used to represent an expression returning value of a certain type. It is used to perform intermediate binding, and will not survive the local rewriting. --> - - - + @@ -394,13 +392,26 @@ + + + + + + + + + + + - - + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index d70a651b8d65f..19fc6c1aa4211 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -33,22 +33,7 @@ Optional IOperation.ConstantValue public abstract TResult Accept(OperationVisitor visitor, TArgument argument); } - internal sealed partial class BoundLValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression - { - protected override OperationKind ExpressionKind => OperationKind.PlaceholderExpression; - - public override void Accept(OperationVisitor visitor) - { - visitor.VisitPlaceholderExpression(this); - } - - public override TResult Accept(OperationVisitor visitor, TArgument argument) - { - return visitor.VisitPlaceholderExpression(this, argument); - } - } - - internal sealed partial class BoundRValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression + internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase, IPlaceholderExpression { protected override OperationKind ExpressionKind => OperationKind.PlaceholderExpression; diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 1b53d6a1af540..49c5e930aca20 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -3112,6 +3112,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Cannot deconstruct a tuple of '{0}' elements into '{1}' variables.. + /// + internal static string ERR_DeconstructWrongCardinality { + get { + return ResourceManager.GetString("ERR_DeconstructWrongCardinality", resourceCulture); + } + } + /// /// Looks up a localized string similar to The Deconstruct method for type '{0}' doesn't have the number of parameters ({1}) needed for this deconstruction.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index aafe2102d7d64..cce4a2d44fe35 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4899,4 +4899,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Option '{0}' must be an absolute path. - + + Cannot deconstruct a tuple of '{0}' elements into '{1}' variables. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 300bb23f27bec..cd9f590566a3e 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1399,5 +1399,6 @@ internal enum ErrorCode ERR_DeconstructRequiresOutParams = 8208, ERR_DeconstructWrongParams = 8209, ERR_DeconstructRequiresExpression = 8210, + ERR_DeconstructWrongCardinality = 8211, } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index 4b7eb0c8c4bf4..ef66ff6fc8d7e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -1485,7 +1485,7 @@ public override BoundNode VisitFixedStatement(BoundFixedStatement node) { foreach (LocalSymbol local in node.Locals) { - switch(local.DeclarationKind) + switch (local.DeclarationKind) { case LocalDeclarationKind.RegularVariable: case LocalDeclarationKind.PatternVariable: @@ -1721,14 +1721,15 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) Assign(node.Left, node.Right, refKind: node.RefKind); return null; } - + public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node) { base.VisitDeconstructionAssignmentOperator(node); - foreach(BoundExpression variable in node.LeftVariables) + foreach (BoundExpression variable in node.LeftVariables) { - Assign(variable, null, refKind: RefKind.None); + // PROTOTYPE(tuples) value should not be set to null + Assign(variable, value: null, refKind: RefKind.None); } return null; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index a3011854e813a..0c7fc2f9e592c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -230,12 +230,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen } } - public override BoundNode VisitLValuePlaceholder(BoundLValuePlaceholder node) - { - return PlaceholderReplacement(node); - } - - public override BoundNode VisitRValuePlaceholder(BoundRValuePlaceholder node) + public override BoundNode VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node) { return PlaceholderReplacement(node); } @@ -287,6 +282,17 @@ private void RemovePlaceholderReplacement(BoundValuePlaceholderBase placeholder) Debug.Assert(removed); } + /// + /// Remove all the listed placeholders. + /// + private void RemovePlaceholderReplacements(ArrayBuilder placeholders) + { + foreach (var placeholder in placeholders) + { + RemovePlaceholderReplacement(placeholder); + } + } + public override BoundNode VisitBadExpression(BoundBadExpression node) { // Cannot recurse into BadExpression children since the BadExpression diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs index ee3bf313d4278..94e3dc6ccc484 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_DeconstructionAssignmentOperator.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -12,76 +11,92 @@ internal sealed partial class LocalRewriter { public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node) { + Debug.Assert(node.DeconstructSteps != null); + Debug.Assert(node.AssignmentSteps != null); + var temps = ArrayBuilder.GetInstance(); var stores = ArrayBuilder.GetInstance(); + var placeholders = ArrayBuilder.GetInstance(); - var lhsReceivers = ArrayBuilder.GetInstance(); - foreach (var variable in node.LeftVariables) - { - // This will be filled in with the LHS that uses temporaries to prevent - // double-evaluation of side effects. - lhsReceivers.Add(TransformCompoundAssignmentLHS(variable, stores, temps, isDynamicAssignment: false)); - } + // evaluate left-hand-side side-effects + var lhsTargets = LeftHandSideSideEffects(node.LeftVariables, temps, stores); + // get or make right-hand-side values BoundExpression loweredRight = VisitExpression(node.Right); - ImmutableArray rhsValues; + AddPlaceholderReplacement(node.DeconstructSteps[0].TargetPlaceholder, loweredRight); + placeholders.Add(node.DeconstructSteps[0].TargetPlaceholder); - // get or make right-hand-side values - if (node.Right.Type.IsTupleType) - { - rhsValues = AccessTupleFields(node, loweredRight, temps, stores); - } - else + foreach (var deconstruction in node.DeconstructSteps) { - rhsValues = CallDeconstruct(node, loweredRight, temps, stores); + if (deconstruction.DeconstructMemberOpt == null) + { + // tuple case + AccessTupleFields(node, deconstruction, temps, stores, placeholders); + } + else + { + CallDeconstruct(node, deconstruction, temps, stores, placeholders); + } } - // assign from rhs values to lhs receivers - int numAssignments = node.Assignments.Length; + int numAssignments = node.AssignmentSteps.Length; for (int i = 0; i < numAssignments; i++) { - // lower the assignment and replace the placeholders for source and target in the process - var assignmentInfo = node.Assignments[i]; - - AddPlaceholderReplacement(assignmentInfo.LValuePlaceholder, lhsReceivers[i]); - AddPlaceholderReplacement(assignmentInfo.RValuePlaceholder, rhsValues[i]); + // lower the assignment and replace the placeholders for its outputs in the process + var assignmentInfo = node.AssignmentSteps[i]; + AddPlaceholderReplacement(assignmentInfo.OutputPlaceholder, lhsTargets[i]); var assignment = VisitExpression(assignmentInfo.Assignment); - RemovePlaceholderReplacement(assignmentInfo.LValuePlaceholder); - RemovePlaceholderReplacement(assignmentInfo.RValuePlaceholder); + RemovePlaceholderReplacement(assignmentInfo.OutputPlaceholder); stores.Add(assignment); } - stores.Add(new BoundVoid(node.Syntax, node.Type)); - var result = _factory.Sequence(temps.ToImmutable(), stores.ToArray()); + var result = _factory.Sequence(temps.ToImmutable(), stores.ToImmutable(), new BoundVoid(node.Syntax, node.Type)); + + RemovePlaceholderReplacements(placeholders); + placeholders.Free(); temps.Free(); stores.Free(); - lhsReceivers.Free(); return result; } - private ImmutableArray AccessTupleFields(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder temps, ArrayBuilder stores) + /// + /// Adds the side effects to stores and returns temporaries (as a flat list) to access them. + /// + private ImmutableArray LeftHandSideSideEffects(ImmutableArray variables, ArrayBuilder temps, ArrayBuilder stores) + { + var lhsReceivers = ArrayBuilder.GetInstance(variables.Length); + + foreach (var variable in variables) + { + // PROTOTYPE(tuples) should the dynamic flag always be false? + lhsReceivers.Add(TransformCompoundAssignmentLHS(variable, stores, temps, isDynamicAssignment: false)); + } + + return lhsReceivers.ToImmutableAndFree(); + } + + private void AccessTupleFields(BoundDeconstructionAssignmentOperator node, BoundDeconstructionDeconstructStep deconstruction, ArrayBuilder temps, ArrayBuilder stores, ArrayBuilder placeholders) { - var tupleType = loweredRight.Type.IsTupleType ? loweredRight.Type : TupleTypeSymbol.Create((NamedTypeSymbol)loweredRight.Type); + var target = PlaceholderReplacement(deconstruction.TargetPlaceholder); + var tupleType = target.Type.IsTupleType ? target.Type : TupleTypeSymbol.Create((NamedTypeSymbol)target.Type); var tupleElementTypes = tupleType.TupleElementTypes; var numElements = tupleElementTypes.Length; - Debug.Assert(numElements == node.LeftVariables.Length); CSharpSyntaxNode syntax = node.Syntax; - // save the loweredRight as we need to access it multiple times + // save the target as we need to access it multiple times BoundAssignmentOperator assignmentToTemp; - var savedTuple = _factory.StoreToTemp(loweredRight, out assignmentToTemp); + var savedTuple = _factory.StoreToTemp(target, out assignmentToTemp); stores.Add(assignmentToTemp); temps.Add(savedTuple.LocalSymbol); // list the tuple fields accessors - var fieldAccessorsBuilder = ArrayBuilder.GetInstance(numElements); var fields = tupleType.TupleElementFields; for (int i = 0; i < numElements; i++) @@ -94,10 +109,10 @@ private ImmutableArray AccessTupleFields(BoundDeconstructionAss Symbol.ReportUseSiteDiagnostic(useSiteInfo, _diagnostics, syntax.Location); } var fieldAccess = MakeTupleFieldAccess(syntax, field, savedTuple, null, LookupResultKind.Empty, tupleElementTypes[i]); - fieldAccessorsBuilder.Add(fieldAccess); - } - return fieldAccessorsBuilder.ToImmutableAndFree(); + AddPlaceholderReplacement(deconstruction.OutputPlaceholders[i], fieldAccess); + placeholders.Add(deconstruction.OutputPlaceholders[i]); + } } /// @@ -105,19 +120,19 @@ private ImmutableArray AccessTupleFields(BoundDeconstructionAss /// Adds a invocation of Deconstruct with those as out parameters onto the 'stores' sequence /// Returns the expressions for those out parameters /// - private ImmutableArray CallDeconstruct(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder temps, ArrayBuilder stores) + private void CallDeconstruct(BoundDeconstructionAssignmentOperator node, BoundDeconstructionDeconstructStep deconstruction, ArrayBuilder temps, ArrayBuilder stores, ArrayBuilder placeholders) { - Debug.Assert((object)node.DeconstructMemberOpt != null); + Debug.Assert((object)deconstruction.DeconstructMemberOpt != null); CSharpSyntaxNode syntax = node.Syntax; // prepare out parameters for Deconstruct - var deconstructParameters = node.DeconstructMemberOpt.Parameters; + var deconstructParameters = deconstruction.DeconstructMemberOpt.Parameters; var outParametersBuilder = ArrayBuilder.GetInstance(deconstructParameters.Length); - Debug.Assert(deconstructParameters.Length == node.LeftVariables.Length); - foreach (var deconstructParameter in deconstructParameters) + for (var i = 0; i < deconstructParameters.Length; i++) { + var deconstructParameter = deconstructParameters[i]; var localSymbol = new SynthesizedLocal(_factory.CurrentMethod, deconstructParameter.Type, SynthesizedLocalKind.LoweringTemp); var localBound = new BoundLocal(syntax, @@ -129,15 +144,16 @@ private ImmutableArray CallDeconstruct(BoundDeconstructionAssig temps.Add(localSymbol); outParametersBuilder.Add(localBound); + + AddPlaceholderReplacement(deconstruction.OutputPlaceholders[i], localBound); + placeholders.Add(deconstruction.OutputPlaceholders[i]); } var outParameters = outParametersBuilder.ToImmutableAndFree(); // invoke Deconstruct - var invokeDeconstruct = MakeCall(syntax, loweredRight, node.DeconstructMemberOpt, outParameters, node.DeconstructMemberOpt.ReturnType); + var invokeDeconstruct = MakeCall(syntax, PlaceholderReplacement(deconstruction.TargetPlaceholder), deconstruction.DeconstructMemberOpt, outParameters, deconstruction.DeconstructMemberOpt.ReturnType); stores.Add(invokeDeconstruct); - - return outParameters; } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index ec91475cfb936..460bfb47f52a1 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -10,6 +10,48 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen [CompilerTrait(CompilerFeature.Tuples)] public class CodeGenDeconstructTests : CSharpTestBase { + const string commonSource = +@"public class Pair +{ + T1 item1; + T2 item2; + + public Pair(T1 item1, T2 item2) + { + this.item1 = item1; + this.item2 = item2; + } + + public void Deconstruct(out T1 item1, out T2 item2) + { + System.Console.WriteLine($""Deconstructing {ToString()}""); + item1 = this.item1; + item2 = this.item2; + } + + public override string ToString() { return $""({item1.ToString()}, {item2.ToString()})""; } +} + +public static class Pair +{ + public static Pair Create(T1 item1, T2 item2) { return new Pair(item1, item2); } +} + +public class Integer +{ + public int state; + public override string ToString() { return state.ToString(); } + public Integer(int i) { state = i; } + public static implicit operator LongInteger(Integer i) { System.Console.WriteLine($""Converting {i}""); return new LongInteger(i.state); } +} + +public class LongInteger +{ + long state; + public LongInteger(long l) { state = l; } + public override string ToString() { return state.ToString(); } +}"; + [Fact] public void SimpleAssign() { @@ -161,7 +203,10 @@ static void Main() Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "f").WithArguments("long", "f").WithLocation(8, 12), // (8,17): error CS1061: 'string' does not contain a definition for 'g' and no extension method 'g' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) // (x.f, y.g) = new C(); - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "g").WithArguments("string", "g").WithLocation(8, 17) + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "g").WithArguments("string", "g").WithLocation(8, 17), + // (8,22): error CS8209: The Deconstruct method for type 'C.Deconstruct()' doesn't have the number of parameters (2) needed for this deconstruction. + // (x.f, y.g) = new C(); + Diagnostic(ErrorCode.ERR_DeconstructWrongParams, "new C()").WithArguments("C.Deconstruct()", "2").WithLocation(8, 22) ); } @@ -280,10 +325,9 @@ class C static void Main() { C c = new C(); - int z; // PROTOTYPE(tuples) this should be removed once the return-type issue is fixed - (c.getHolderForX().x, c.getHolderForY().y, z) = c.getDeconstructReceiver(); + (c.getHolderForX().x, c.getHolderForY().y) = c.getDeconstructReceiver(); } - public void Deconstruct(out D1 x, out D2 y, out int z) { x = new D1(); y = new D2(); z = 3; Console.WriteLine(""Deconstruct""); } + public void Deconstruct(out D1 x, out D2 y) { x = new D1(); y = new D2(); Console.WriteLine(""Deconstruct""); } } class D1 { @@ -454,47 +498,15 @@ public void Deconstruct(out int a, out int b) "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,18): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) + // (8,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "new C()").WithArguments("int", "byte").WithLocation(8, 18), - // (8,18): error CS0029: Cannot implicitly convert type 'int' to 'string' + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = new C()").WithArguments("int", "byte").WithLocation(8, 9), + // (8,9): error CS0029: Cannot implicitly convert type 'int' to 'string' // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "new C()").WithArguments("int", "string").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = new C()").WithArguments("int", "string").WithLocation(8, 9) ); } - [Fact(Skip = "PROTOTYPE(tuples)")] - public void Nesting() - { - string source = @" -class C -{ - static void Main() - { - int x, y, z; - (x, (y, z)) = new C(); - } - - public void Deconstruct(out int a, out D d) - { - a = 1; - d = new D(); - } -} -class D -{ - public void Deconstruct(out string b, out string c) - { - b = ""hello""; - c = ""world""; - } -} -"; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); - // expect a console output - } - [Fact] public void ExpressionType() { @@ -959,57 +971,57 @@ .maxstack 10 .locals init (long V_0, //x int V_1) //y IL_0000: ldc.i4.1 - IL_0001: conv.i8 + IL_0001: ldc.i4.1 IL_0002: ldc.i4.1 - IL_0003: conv.i8 + IL_0003: ldc.i4.1 IL_0004: ldc.i4.1 - IL_0005: conv.i8 + IL_0005: ldc.i4.1 IL_0006: ldc.i4.1 - IL_0007: conv.i8 - IL_0008: ldc.i4.1 - IL_0009: conv.i8 - IL_000a: ldc.i4.1 - IL_000b: conv.i8 - IL_000c: ldc.i4.1 - IL_000d: conv.i8 - IL_000e: ldc.i4.1 - IL_000f: conv.i8 - IL_0010: ldc.i4.4 - IL_0011: conv.i8 - IL_0012: ldc.i4.2 - IL_0013: newobj ""System.ValueTuple..ctor(long, long, int)"" - IL_0018: newobj ""System.ValueTuple..ctor(long, long, long, long, long, long, long, (long, long, int))"" - IL_001d: dup - IL_001e: ldfld ""long System.ValueTuple.Item1"" + IL_0007: ldc.i4.1 + IL_0008: ldc.i4.4 + IL_0009: ldc.i4.2 + IL_000a: newobj ""System.ValueTuple..ctor(int, int, int)"" + IL_000f: newobj ""System.ValueTuple..ctor(int, int, int, int, int, int, int, (int, int, int))"" + IL_0014: dup + IL_0015: ldfld ""int System.ValueTuple.Item1"" + IL_001a: conv.i8 + IL_001b: stloc.0 + IL_001c: dup + IL_001d: ldfld ""int System.ValueTuple.Item2"" + IL_0022: conv.i8 IL_0023: stloc.0 IL_0024: dup - IL_0025: ldfld ""long System.ValueTuple.Item2"" - IL_002a: stloc.0 - IL_002b: dup - IL_002c: ldfld ""long System.ValueTuple.Item3"" - IL_0031: stloc.0 - IL_0032: dup - IL_0033: ldfld ""long System.ValueTuple.Item4"" - IL_0038: stloc.0 - IL_0039: dup - IL_003a: ldfld ""long System.ValueTuple.Item5"" - IL_003f: stloc.0 - IL_0040: dup - IL_0041: ldfld ""long System.ValueTuple.Item6"" - IL_0046: stloc.0 - IL_0047: dup - IL_0048: ldfld ""long System.ValueTuple.Item7"" - IL_004d: stloc.0 - IL_004e: dup - IL_004f: ldfld ""(long, long, int) System.ValueTuple.Rest"" - IL_0054: ldfld ""long System.ValueTuple.Item1"" - IL_0059: stloc.0 - IL_005a: dup - IL_005b: ldfld ""(long, long, int) System.ValueTuple.Rest"" - IL_0060: ldfld ""long System.ValueTuple.Item2"" + IL_0025: ldfld ""int System.ValueTuple.Item3"" + IL_002a: conv.i8 + IL_002b: stloc.0 + IL_002c: dup + IL_002d: ldfld ""int System.ValueTuple.Item4"" + IL_0032: conv.i8 + IL_0033: stloc.0 + IL_0034: dup + IL_0035: ldfld ""int System.ValueTuple.Item5"" + IL_003a: conv.i8 + IL_003b: stloc.0 + IL_003c: dup + IL_003d: ldfld ""int System.ValueTuple.Item6"" + IL_0042: conv.i8 + IL_0043: stloc.0 + IL_0044: dup + IL_0045: ldfld ""int System.ValueTuple.Item7"" + IL_004a: conv.i8 + IL_004b: stloc.0 + IL_004c: dup + IL_004d: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_0052: ldfld ""int System.ValueTuple.Item1"" + IL_0057: conv.i8 + IL_0058: stloc.0 + IL_0059: dup + IL_005a: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_005f: ldfld ""int System.ValueTuple.Item2"" + IL_0064: conv.i8 IL_0065: stloc.0 - IL_0066: ldfld ""(long, long, int) System.ValueTuple.Rest"" - IL_006b: ldfld ""int System.ValueTuple.Item3"" + IL_0066: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_006b: ldfld ""int System.ValueTuple.Item3"" IL_0070: stloc.1 IL_0071: ldloc.0 IL_0072: box ""long"" @@ -1125,12 +1137,14 @@ static void Main() } } "; - // PROTOTYPE(tuples) This error message is misleading var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (9,18): error CS0029: Cannot implicitly convert type '(int, int)' to '(byte, string)' + // (9,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) + // (x, y) = (1, 2); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = (1, 2)").WithArguments("int", "byte").WithLocation(9, 9), + // (9,9): error CS0029: Cannot implicitly convert type 'int' to 'string' // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("(int, int)", "(byte, string)").WithLocation(9, 18) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = (1, 2)").WithArguments("int", "string").WithLocation(9, 9) ); } @@ -1343,5 +1357,374 @@ public void Deconstruct(out int a, out int b) Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "z = ((x, y) = new C())").WithArguments("void").WithLocation(10, 13) ); } + + [Fact] + public void NestedTupleAssignment() + { + string source = @" +class C +{ + static void Main() + { + int x; + string y, z; + + (x, (y, z)) = (1, (""a"", ""b"")); + System.Console.WriteLine(x + "" "" + y + "" "" + z); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "1 a b", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void NestedTypelessTupleAssignment() + { + string source = @" +class C +{ + static void Main() + { + string x, y, z; + + (x, (y, z)) = (null, (null, null)); + System.Console.WriteLine(""nothing"" + x + y + z); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "nothing", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact(Skip = "PROTOTYPE(tuples)")] + public void NestedTypelessTupleAssignment2() + { + string source = @" +class C +{ + static void Main() + { + int x, y, z; // int cannot be null + + (x, (y, z)) = (null, (null, null)); + System.Console.WriteLine(""nothing"" + x + y + z); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "nothing", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void NestedDeconstructAssignment() + { + string source = @" +class C +{ + static void Main() + { + int x; + string y, z; + + (x, (y, z)) = new D1(); + System.Console.WriteLine(x + "" "" + y + "" "" + z); + } +} +class D1 +{ + public void Deconstruct(out int item1, out D2 item2) + { + item1 = 1; + item2 = new D2(); + } +} +class D2 +{ + public void Deconstruct(out string item1, out string item2) + { + item1 = ""a""; + item2 = ""b""; + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "1 a b", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void NestedMixedAssignment1() + { + string source = @" +class C +{ + static void Main() + { + int x, y, z; + + (x, (y, z)) = (1, new D1()); + System.Console.WriteLine(x + "" "" + y + "" "" + z); + } +} +class D1 +{ + public void Deconstruct(out int item1, out int item2) + { + item1 = 2; + item2 = 3; + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "1 2 3", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void NestedMixedAssignment2() + { + string source = @" +class C +{ + static void Main() + { + int x; + string y, z; + + (x, (y, z)) = new D1(); + System.Console.WriteLine(x + "" "" + y + "" "" + z); + } +} +class D1 +{ + public void Deconstruct(out int item1, out (string, string) item2) + { + item1 = 1; + item2 = (""a"", ""b""); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "1 a b", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void VerifyNestedExecutionOrder() + { + string source = @" +using System; +class C +{ + int x { set { Console.WriteLine($""setX""); } } + int y { set { Console.WriteLine($""setY""); } } + int z { set { Console.WriteLine($""setZ""); } } + + C getHolderForX() { Console.WriteLine(""getHolderforX""); return this; } + C getHolderForY() { Console.WriteLine(""getHolderforY""); return this; } + C getHolderForZ() { Console.WriteLine(""getHolderforZ""); return this; } + C getDeconstructReceiver() { Console.WriteLine(""getDeconstructReceiver""); return this; } + + static void Main() + { + C c = new C(); + (c.getHolderForX().x, (c.getHolderForY().y, c.getHolderForZ().z)) = c.getDeconstructReceiver(); + } + public void Deconstruct(out D1 x, out C1 t) { x = new D1(); t = new C1(); Console.WriteLine(""Deconstruct1""); } +} +class C1 +{ + public void Deconstruct(out D2 y, out D3 z) { y = new D2(); z = new D3(); Console.WriteLine(""Deconstruct2""); } +} +class D1 +{ + public static implicit operator int(D1 d) { Console.WriteLine(""Conversion1""); return 1; } +} +class D2 +{ + public static implicit operator int(D2 d) { Console.WriteLine(""Conversion2""); return 2; } +} +class D3 +{ + public static implicit operator int(D3 d) { Console.WriteLine(""Conversion3""); return 3; } +} +"; + + string expected = +@"getHolderforX +getHolderforY +getHolderforZ +getDeconstructReceiver +Deconstruct1 +Deconstruct2 +Conversion1 +setX +Conversion2 +setY +Conversion3 +setZ +"; + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void VerifyNestedExecutionOrder2() + { + string source = @" +using System; +class C +{ + static LongInteger x1 { set { Console.WriteLine($""setX1 {value}""); } } + static LongInteger x2 { set { Console.WriteLine($""setX2 {value}""); } } + static LongInteger x3 { set { Console.WriteLine($""setX3 {value}""); } } + static LongInteger x4 { set { Console.WriteLine($""setX4 {value}""); } } + static LongInteger x5 { set { Console.WriteLine($""setX5 {value}""); } } + static LongInteger x6 { set { Console.WriteLine($""setX6 {value}""); } } + static LongInteger x7 { set { Console.WriteLine($""setX7 {value}""); } } + + static void Main() + { + ((x1, (x2, x3)), ((x4, x5), (x6, x7))) = Pair.Create(Pair.Create(new Integer(1), Pair.Create(new Integer(2), new Integer(3))), + Pair.Create(Pair.Create(new Integer(4), new Integer(5)), Pair.Create(new Integer(6), new Integer(7)))); + } +} +" + commonSource; + + string expected = +@"Deconstructing ((1, (2, 3)), ((4, 5), (6, 7))) +Deconstructing (1, (2, 3)) +Deconstructing (2, 3) +Deconstructing ((4, 5), (6, 7)) +Deconstructing (4, 5) +Deconstructing (6, 7) +Converting 1 +setX1 1 +Converting 2 +setX2 2 +Converting 3 +setX3 3 +Converting 4 +setX4 4 +Converting 5 +setX5 5 +Converting 6 +setX6 6 +Converting 7 +setX7 7"; + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MixOfAssignments() + { + string source = @" +class C +{ + static void Main() + { + long x; + string y; + + C a, b, c; + c = new C(); + (x, y) = a = b = c; + System.Console.WriteLine(x + "" "" + y); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 hello", parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact(Skip = "PROTOTYPE(tuples)")] + public void AssignWithPostfixOperator() + { + string source = @" +class C +{ + int state = 1; + + static void Main() + { + long x; + string y; + C c = new C(); + (x, y) = c++; + System.Console.WriteLine(x + "" "" + y); + } + + public void Deconstruct(out int a, out string b) + { + a = state; + b = ""hello""; + } + + public static C operator ++(C c1) + { + return new C() { state = 2 }; + } +} +"; + // PROTOTYPE(tuples) we expect "2 hello" instead + var comp = CompileAndVerify(source, expectedOutput: "1 hello", parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void TupleWithWrongCardinality() + { + string source = @" +class C +{ + static void Main() + { + int x, y, z; + + (x, y, z) = MakePair(); + } + + public static (int, int) MakePair() + { + return (42, 42); + } +} +"; + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (8,9): error CS8211: Cannot deconstruct a tuple of '2' elements into '3' variables. + // (x, y, z) = MakePair(); + Diagnostic(ErrorCode.ERR_DeconstructWrongCardinality, "(x, y, z) = MakePair()").WithArguments("2", "3").WithLocation(8, 9) + ); + } + + [Fact] + public void NestedTupleWithWrongCardinality() + { + string source = @" +class C +{ + static void Main() + { + int x, y, z, w; + + (x, (y, z, w)) = Pair.Create(42, (43, 44)); + } +} +" + commonSource; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (8,9): error CS8211: Cannot deconstruct a tuple of '2' elements into '3' variables. + // (x, (y, z, w)) = Pair.Create(42, (43, 44)); + Diagnostic(ErrorCode.ERR_DeconstructWrongCardinality, "(x, (y, z, w)) = Pair.Create(42, (43, 44))").WithArguments("2", "3").WithLocation(8, 9) + ); + } } } \ No newline at end of file