static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType) { if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType)) { return(false); } if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) { return(false); // conv does not match binary operation } return(true); }
/// <summary> /// Transform compound assignments where the return value is not being used, /// or where there's an inlined assignment within the setter call. /// /// Patterns handled: /// 1. /// callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value)) /// ==> compound.op.new(callvirt get_Property(ldloc S_1), value) /// 2. /// callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value))) /// ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value)) /// 3. /// stobj(target, binary.op(ldobj(target), ...)) /// where target is pure /// => compound.op(target, ...) /// </summary> /// <remarks> /// Called by ExpressionTransforms, or after the inline-assignment transform for setters. /// </remarks> internal static bool HandleCompoundAssign(ILInstruction compoundStore, StatementTransformContext context) { if (!context.Settings.MakeAssignmentExpressions || !context.Settings.IntroduceIncrementAndDecrement) { return(false); } if (compoundStore is CallInstruction && compoundStore.SlotInfo != Block.InstructionSlot) { // replacing 'call set_Property' with a compound assignment instruction // changes the return value of the expression, so this is only valid on block-level. return(false); } if (!IsCompoundStore(compoundStore, out var targetType, out var setterValue, context.TypeSystem)) { return(false); } // targetType = The type of the property/field/etc. being stored to. // setterValue = The value being stored. var storeInSetter = setterValue as StLoc; if (storeInSetter != null) { // We'll move the stloc to top-level: // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value))) // ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value)) setterValue = storeInSetter.Value; if (storeInSetter.Variable.Type.IsSmallIntegerType()) { // 'stloc v' implicitly truncates the value. // Ensure that type of 'v' matches the type of the property: if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize()) { return(false); } if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign()) { return(false); } } } ILInstruction newInst; if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary) { if (compoundStore is StLoc) { // transform local variables only for user-defined operators return(false); } if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) { return(false); } if (!ValidateCompoundAssign(binary, smallIntConv, targetType)) { return(false); } context.Step($"Compound assignment (binary.numeric)", compoundStore); finalizeMatch?.Invoke(context); newInst = new NumericCompoundAssign( binary, target, targetKind, binary.Right, targetType, CompoundEvalMode.EvaluatesToNewValue); } else if (setterValue is Call operatorCall && operatorCall.Method.IsOperator) { if (operatorCall.Arguments.Count == 0) { return(false); } if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) { return(false); } ILInstruction rhs; if (operatorCall.Arguments.Count == 2) { if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null) { return(false); } rhs = operatorCall.Arguments[1]; } else if (operatorCall.Arguments.Count == 1) { if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement")) { return(false); } // use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls rhs = new LdcI4(1); } else { return(false); } if (operatorCall.IsLifted) { return(false); // TODO: add tests and think about whether nullables need special considerations } context.Step($"Compound assignment (user-defined binary)", compoundStore); finalizeMatch?.Invoke(context); newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToNewValue, target, targetKind, rhs); }