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); }
/// <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)); }
/// <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)); }
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); }
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); }