private BoundExpression RewriteDeconstruction(
            ArrayBuilder <Binder.DeconstructionVariable> lhsTargets,
            Conversion conversion,
            TypeSymbol leftType,
            BoundExpression right,
            bool isUsed)
        {
            if (right.Kind == BoundKind.ConditionalOperator)
            {
                var conditional = (BoundConditionalOperator)right;
                Debug.Assert(!conditional.IsRef);
                return(conditional.Update(
                           conditional.IsRef,
                           VisitExpression(conditional.Condition),
                           RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Consequence, isUsed: true),
                           RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.Alternative, isUsed: true),
                           conditional.ConstantValue,
                           leftType));
            }

            var temps = ArrayBuilder <LocalSymbol> .GetInstance();

            var             effects     = DeconstructionSideEffects.GetInstance();
            BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true);

            effects.Consolidate();

            if (!isUsed)
            {
                // When a deconstruction is not used, the last effect is used as return value
                Debug.Assert(returnValue is null);
                var last = effects.PopLast();
                if (last is null)
                {
                    temps.Free();
                    effects.Free();
                    // Deconstructions with no effects lower to nothing. For example, `(_, _) = (1, 2);`
                    return(null);
                }

                return(_factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), last));
            }
            else
            {
                if (!returnValue.HasErrors)
                {
                    returnValue = VisitExpression(returnValue);
                }

                return(_factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue));
            }
        }
        /// <summary>
        /// This method recurses through leftTargets, right and conversion at the same time.
        /// As it does, it collects side-effects into the proper buckets (init, deconstructions, conversions, assignments).
        ///
        /// The side-effects from the right initially go into the init bucket. But once we started drilling into a Deconstruct
        /// invocation, subsequent side-effects from the right go into the deconstructions bucket (otherwise they would
        /// be evaluated out of order).
        /// </summary>
        private BoundExpression ApplyDeconstructionConversion(
            ArrayBuilder <Binder.DeconstructionVariable> leftTargets,
            BoundExpression right,
            Conversion conversion,
            ArrayBuilder <LocalSymbol> temps,
            DeconstructionSideEffects effects,
            bool isUsed, bool inInit)
        {
            Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);
            ImmutableArray <BoundExpression> rightParts = GetRightParts(right, conversion, temps, effects, ref inInit);

            ImmutableArray <Conversion> underlyingConversions = conversion.UnderlyingConversions;

            Debug.Assert(!underlyingConversions.IsDefault);
            Debug.Assert(leftTargets.Count == rightParts.Length && leftTargets.Count == conversion.UnderlyingConversions.Length);

            var builder = isUsed ? ArrayBuilder <BoundExpression> .GetInstance(leftTargets.Count) : null;

            for (int i = 0; i < leftTargets.Count; i++)
            {
                BoundExpression resultPart;
                if (leftTargets[i].HasNestedVariables)
                {
                    resultPart = ApplyDeconstructionConversion(leftTargets[i].NestedVariables, rightParts[i],
                                                               underlyingConversions[i], temps, effects, isUsed, inInit);
                }
                else
                {
                    var rightPart = rightParts[i];
                    if (inInit)
                    {
                        rightPart = EvaluateSideEffectingArgumentToTemp(rightPart, effects.init, temps);
                    }
                    BoundExpression leftTarget = leftTargets[i].Single;

                    resultPart = EvaluateConversionToTemp(rightPart, underlyingConversions[i], leftTarget.Type, temps,
                                                          effects.conversions);

                    if (leftTarget.Kind != BoundKind.DiscardExpression)
                    {
                        effects.assignments.Add(MakeAssignmentOperator(resultPart.Syntax, leftTarget, resultPart, leftTarget.Type,
                                                                       used: true, isChecked: false, isCompoundAssignment: false));
                    }
                }
                builder?.Add(resultPart);
            }

            if (isUsed)
            {
                var tupleType = TupleTypeSymbol.Create(locationOpt: null, elementTypesWithAnnotations: builder.SelectAsArray(e => TypeWithAnnotations.Create(e.Type)),
                                                       elementLocations: default, elementNames: default,
            internal static DeconstructionSideEffects GetInstance()
            {
                var result = new DeconstructionSideEffects();

                result.init = ArrayBuilder <BoundExpression> .GetInstance();

                result.deconstructions = ArrayBuilder <BoundExpression> .GetInstance();

                result.conversions = ArrayBuilder <BoundExpression> .GetInstance();

                result.assignments = ArrayBuilder <BoundExpression> .GetInstance();

                return(result);
            }
Esempio n. 4
0
        /// <summary>
        /// The left represents a tree of L-values. The structure of right can be missing parts of the tree on the left.
        /// The conversion holds nested conversisons and deconstruction information, which matches the tree from the left,
        /// and it provides the information to fill in the missing parts of the tree from the right and convert it to
        /// the tree from the left.
        ///
        /// A bound sequence is returned which has different phases of side-effects:
        /// - the initialization phase includes side-effects from the left, followed by evaluations of the right
        /// - the deconstruction phase includes all the invocations of Deconstruct methods and tuple element accesses below a Deconstruct call
        /// - the conversion phase
        /// - the assignment phase
        /// </summary>
        private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right)
        {
            var temps = ArrayBuilder <LocalSymbol> .GetInstance();

            var effects = DeconstructionSideEffects.GetInstance();
            ArrayBuilder <Binder.DeconstructionVariable> lhsTargets = GetAssignmentTargetsAndSideEffects(left, temps, effects.init);

            BoundExpression returnTuple = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, inInit: true);

            if (!returnTuple.HasErrors)
            {
                returnTuple = VisitExpression(returnTuple);
            }
            BoundExpression result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnTuple);

            Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);

            return(result);
        }
        /// <summary>
        /// The left represents a tree of L-values. The structure of right can be missing parts of the tree on the left.
        /// The conversion holds nested conversions and deconstruction information, which matches the tree from the left,
        /// and it provides the information to fill in the missing parts of the tree from the right and convert it to
        /// the tree from the left.
        ///
        /// A bound sequence is returned which has different phases of side-effects:
        /// - the initialization phase includes side-effects from the left, followed by evaluations of the right
        /// - the deconstruction phase includes all the invocations of Deconstruct methods and tuple element accesses below a Deconstruct call
        /// - the conversion phase
        /// - the assignment phase
        /// </summary>
        private BoundExpression RewriteDeconstruction(BoundTupleExpression left, Conversion conversion, BoundExpression right, bool isUsed)
        {
            var temps = ArrayBuilder <LocalSymbol> .GetInstance();

            var effects = DeconstructionSideEffects.GetInstance();
            ArrayBuilder <Binder.DeconstructionVariable> lhsTargets = GetAssignmentTargetsAndSideEffects(left, temps, effects.init);

            BoundExpression returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, isUsed, inInit: true);

            effects.Consolidate();

            BoundExpression result;

            if (!isUsed)
            {
                // When a deconstruction is not used, the last effect is used as return value
                Debug.Assert(returnValue is null);
                var last = effects.PopLast();
                if (last is null)
                {
                    // Deconstructions with no effects lower to nothing. For example, `(_, _) = (1, 2);`
                    result = null;
                    temps.Free();
                    _ = effects.ToImmutableAndFree();
                }
                else
                {
                    result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), last);
                }
            }
            else
            {
                if (!returnValue.HasErrors)
                {
                    returnValue = VisitExpression(returnValue);
                }
                result = _factory.Sequence(temps.ToImmutableAndFree(), effects.ToImmutableAndFree(), returnValue);
            }

            Binder.DeconstructionVariable.FreeDeconstructionVariables(lhsTargets);
            return(result);
        }
        private Expression RewriteDeconstruction(List <DeconstructionVariable> lhsTargets, Conversion conversion, Type leftType, Expression right, Func <Type, ParameterExpression> createTemp)
        {
            if (right is ConditionalExpression conditional)
            {
                return
                    (conditional.Update(
                         conditional.Test,
                         RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.IfTrue, createTemp),
                         RewriteDeconstruction(lhsTargets, conversion, leftType, conditional.IfFalse, createTemp)
                         ));
            }

            var temps   = new List <ParameterExpression>();
            var effects = new DeconstructionSideEffects();

            var returnValue = ApplyDeconstructionConversion(lhsTargets, right, conversion, temps, effects, inInit: true, createTemp);

            var stmts = effects.Consolidate();

            stmts.Add(returnValue);

            return(Expression.Block(temps, stmts));
        }
Esempio n. 7
0
        /// <summary>
        /// This method recurses through leftTargets, right and conversion at the same time.
        /// As it does, it collects side-effects into the proper buckets (init, deconstructions, conversions, assignments).
        ///
        /// The side-effects from the right initially go into the init bucket. But once we started drilling into a Deconstruct
        /// invocation, subsequent side-effects from the right go into the deconstructions bucket (otherwise they would
        /// be evaluated out of order).
        /// </summary>
        private BoundTupleLiteral ApplyDeconstructionConversion(ArrayBuilder <Binder.DeconstructionVariable> leftTargets,
                                                                BoundExpression right, Conversion conversion, ArrayBuilder <LocalSymbol> temps, DeconstructionSideEffects effects,
                                                                bool inInit)
        {
            Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);
            ImmutableArray <BoundExpression> rightParts = GetRightParts(right, conversion, ref temps, effects, ref inInit);

            ImmutableArray <Conversion> underlyingConversions = conversion.UnderlyingConversions;

            Debug.Assert(!underlyingConversions.IsDefault);
            Debug.Assert(leftTargets.Count == rightParts.Length && leftTargets.Count == conversion.UnderlyingConversions.Length);

            var builder = ArrayBuilder <BoundExpression> .GetInstance(leftTargets.Count);

            for (int i = 0; i < leftTargets.Count; i++)
            {
                BoundExpression resultPart;
                if (leftTargets[i].HasNestedVariables)
                {
                    resultPart = ApplyDeconstructionConversion(leftTargets[i].NestedVariables, rightParts[i],
                                                               underlyingConversions[i], temps, effects, inInit);
                }
                else
                {
                    var rightPart = rightParts[i];
                    if (inInit)
                    {
                        rightPart = EvaluateSideEffectingArgumentToTemp(VisitExpression(rightPart), inInit ? effects.init : effects.deconstructions, ref temps);
                    }
                    BoundExpression leftTarget = leftTargets[i].Single;

                    resultPart = EvaluateConversionToTemp(rightPart, underlyingConversions[i], leftTarget.Type, temps,
                                                          effects.conversions);

                    if (leftTarget.Kind != BoundKind.DiscardExpression)
                    {
                        effects.assignments.Add(MakeAssignmentOperator(resultPart.Syntax, leftTarget, resultPart, leftTarget.Type,
                                                                       used: true, isChecked: false, isCompoundAssignment: false));
                    }
                }
                builder.Add(resultPart);
            }

            var tupleType = TupleTypeSymbol.Create(locationOpt: null, elementTypes: builder.SelectAsArray(e => e.Type),
                                                   elementLocations: default(ImmutableArray <Location>), elementNames: default(ImmutableArray <string>),
                                                   compilation: _compilation, shouldCheckConstraints: false);

            return(new BoundTupleLiteral(right.Syntax, default(ImmutableArray <string>), builder.ToImmutableAndFree(), tupleType));
        }
Esempio n. 8
0
        private ImmutableArray <BoundExpression> GetRightParts(BoundExpression right, Conversion conversion,
                                                               ref ArrayBuilder <LocalSymbol> temps, DeconstructionSideEffects effects, ref bool inInit)
        {
            ImmutableArray <BoundExpression> rightParts;

            var deconstructionInfo = conversion.DeconstructionInfo;

            if (!deconstructionInfo.IsDefault)
            {
                Debug.Assert(right.Kind != BoundKind.TupleLiteral && right.Kind != BoundKind.ConvertedTupleLiteral);

                BoundExpression evaluationResult = EvaluateSideEffectingArgumentToTemp(VisitExpression(right),
                                                                                       inInit ? effects.init : effects.deconstructions, ref temps);

                rightParts = InvokeDeconstructMethod(deconstructionInfo, evaluationResult, effects.deconstructions, ref temps);
                inInit     = false;
            }
            else if (right.Kind == BoundKind.TupleLiteral || right.Kind == BoundKind.ConvertedTupleLiteral)
            {
                rightParts = ((BoundTupleExpression)right).Arguments;
            }
            else if (right.Kind == BoundKind.Conversion)
            {
                var tupleConversion = (BoundConversion)right;
                Debug.Assert(tupleConversion.Conversion.Kind == ConversionKind.ImplicitTupleLiteral);
                rightParts = ((BoundTupleExpression)tupleConversion.Operand).Arguments;
            }
            else if (right.Type.IsTupleType)
            {
                rightParts = AccessTupleFields(VisitExpression(right), temps, effects.deconstructions);
                inInit     = false;
            }
            else
            {
                throw ExceptionUtilities.Unreachable;
            }

            return(rightParts);
        }
Esempio n. 9
0
        private ImmutableArray <BoundExpression> GetRightParts(BoundExpression right, Conversion conversion,
                                                               ref ArrayBuilder <LocalSymbol> temps, DeconstructionSideEffects effects, ref bool inInit)
        {
            // Example:
            // var (x, y) = new Point(1, 2);
            var deconstructionInfo = conversion.DeconstructionInfo;

            if (!deconstructionInfo.IsDefault)
            {
                Debug.Assert(!IsTupleExpression(right.Kind));

                BoundExpression evaluationResult = EvaluateSideEffectingArgumentToTemp(VisitExpression(right),
                                                                                       inInit ? effects.init : effects.deconstructions, ref temps);

                inInit = false;
                return(InvokeDeconstructMethod(deconstructionInfo, evaluationResult, effects.deconstructions, ref temps));
            }

            // Example:
            // var (x, y) = (1, 2);
            if (IsTupleExpression(right.Kind))
            {
                return(((BoundTupleExpression)right).Arguments);
            }

            // Example:
            // (byte x, byte y) = (1, 2);
            // (int x, string y) = (1, null);
            if (right.Kind == BoundKind.Conversion)
            {
                var tupleConversion = (BoundConversion)right;
                if ((tupleConversion.Conversion.Kind == ConversionKind.ImplicitTupleLiteral || tupleConversion.Conversion.Kind == ConversionKind.Identity) &&
                    IsTupleExpression(tupleConversion.Operand.Kind))
                {
                    return(((BoundTupleExpression)tupleConversion.Operand).Arguments);
                }
            }

            // Example:
            // var (x, y) = GetTuple();
            // var (x, y) = ((byte, byte)) (1, 2);
            // var (a, _) = ((short, short))((int, int))(1L, 2L);
            if (right.Type.IsTupleType)
            {
                inInit = false;
                return(AccessTupleFields(VisitExpression(right), temps, effects.deconstructions));
            }

            throw ExceptionUtilities.Unreachable;
        }
        private Expression ApplyDeconstructionConversion(List <DeconstructionVariable> leftTargets, Expression right, Conversion conversion, List <ParameterExpression> temps, DeconstructionSideEffects effects, bool inInit, Func <Type, ParameterExpression> createTemp)
        {
            Debug.Assert(conversion is DeconstructionConversion);

            var rightParts = GetRightParts(right, conversion, temps, effects, ref inInit, createTemp);

            var deconstruct = (DeconstructionConversion)conversion;

            var conversions = deconstruct.Conversions;

            Debug.Assert(leftTargets.Count == rightParts.Count && leftTargets.Count == conversions.Count);

            var builder = new List <Expression>(leftTargets.Count);

            for (int i = 0; i < leftTargets.Count; i++)
            {
                Expression resultPart;

                var nestedConversion = conversions[i];

                if (leftTargets[i].NestedVariables is { } nested)
                {
                    resultPart = ApplyDeconstructionConversion(nested, rightParts[i], nestedConversion, temps, effects, inInit, createTemp);
                }