// Returns null if the operator can't be evaluated at compile time. private ConstantValue FoldBinaryOperator( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, SpecialType resultType, DiagnosticBag diagnostics, ref int compoundStringLength) { Debug.Assert(left != null); Debug.Assert(right != null); if (left.HasAnyErrors || right.HasAnyErrors) { return null; } // SPEC VIOLATION: see method definition for details ConstantValue nullableEqualityResult = TryFoldingNullableEquality(kind, left, right); if (nullableEqualityResult != null) { return nullableEqualityResult; } var valueLeft = left.ConstantValue; var valueRight = right.ConstantValue; if (valueLeft == null || valueRight == null) { return null; } if (valueLeft.IsBad || valueRight.IsBad) { return ConstantValue.Bad; } if (kind.IsEnum() && !kind.IsLifted()) { return FoldEnumBinaryOperator(syntax, kind, left, right, diagnostics); } // Divisions by zero on integral types and decimal always fail even in an unchecked context. if (IsDivisionByZero(kind, valueRight)) { Error(diagnostics, ErrorCode.ERR_IntDivByZero, syntax); return ConstantValue.Bad; } object newValue = null; // Certain binary operations never fail; bool & bool, for example. If we are in one of those // cases, simply fold the operation and return. // // Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1 // (regardless of checked context) the constant folding behavior is different. // Remainder never overflows at compile time while division does. newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight); if (newValue != null) { return ConstantValue.Create(newValue, resultType); } ConstantValue concatResult = FoldStringConcatenation(kind, valueLeft, valueRight, ref compoundStringLength); if (concatResult != null) { if (concatResult.IsBad) { Error(diagnostics, ErrorCode.ERR_ConstantStringTooLong, syntax); } return concatResult; } // Certain binary operations always fail if they overflow even when in an unchecked context; // decimal + decimal, for example. If we are in one of those cases, make the attempt. If it // succeeds, return the result. If not, give a compile-time error regardless of context. try { newValue = FoldDecimalBinaryOperators(kind, valueLeft, valueRight); } catch (OverflowException) { Error(diagnostics, ErrorCode.ERR_DecConstError, syntax); return ConstantValue.Bad; } if (newValue != null) { return ConstantValue.Create(newValue, resultType); } if (CheckOverflowAtCompileTime) { try { newValue = FoldCheckedIntegralBinaryOperator(kind, valueLeft, valueRight); } catch (OverflowException) { Error(diagnostics, ErrorCode.ERR_CheckedOverflow, syntax); return ConstantValue.Bad; } } else { newValue = FoldUncheckedIntegralBinaryOperator(kind, valueLeft, valueRight); } if (newValue != null) { return ConstantValue.Create(newValue, resultType); } return null; }
private ConstantValue FoldEnumBinaryOperator( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, DiagnosticBag diagnostics) { Debug.Assert(left != null); Debug.Assert(right != null); Debug.Assert(kind.IsEnum()); Debug.Assert(!kind.IsLifted()); // A built-in binary operation on constant enum operands is evaluated into an operation on // constants of the underlying type U of the enum type E. Comparison operators are lowered as // simply computing U<U. All other operators are computed as (E)(U op U) or in the case of // E-E, (U)(U-U). TypeSymbol enumType = GetEnumType(kind, left, right); TypeSymbol underlyingType = enumType.GetEnumUnderlyingType(); BoundExpression newLeftOperand = CreateConversion(left, underlyingType, diagnostics); BoundExpression newRightOperand = CreateConversion(right, underlyingType, diagnostics); // If the underlying type is byte, sbyte, short, ushort or nullables of those then we'll need // to convert it up to int or int? because there are no + - * & | ^ < > <= >= == != operators // on byte, sbyte, short or ushort. They all convert to int. SpecialType operandSpecialType = GetEnumPromotedType(underlyingType.SpecialType); TypeSymbol operandType = (operandSpecialType == underlyingType.SpecialType) ? underlyingType : GetSpecialType(operandSpecialType, diagnostics, syntax); newLeftOperand = CreateConversion(newLeftOperand, operandType, diagnostics); newRightOperand = CreateConversion(newRightOperand, operandType, diagnostics); BinaryOperatorKind newKind = kind.Operator().WithType(newLeftOperand.Type.SpecialType); SpecialType operatorType = SpecialType.None; switch (newKind.Operator()) { case BinaryOperatorKind.Addition: case BinaryOperatorKind.Subtraction: case BinaryOperatorKind.And: case BinaryOperatorKind.Or: case BinaryOperatorKind.Xor: operatorType = operandType.SpecialType; break; case BinaryOperatorKind.LessThan: case BinaryOperatorKind.LessThanOrEqual: case BinaryOperatorKind.GreaterThan: case BinaryOperatorKind.GreaterThanOrEqual: case BinaryOperatorKind.Equal: case BinaryOperatorKind.NotEqual: operatorType = SpecialType.System_Boolean; break; default: throw ExceptionUtilities.UnexpectedValue(newKind.Operator()); } var constantValue = FoldBinaryOperator(syntax, newKind, newLeftOperand, newRightOperand, operatorType, diagnostics); if (operatorType != SpecialType.System_Boolean && constantValue != null && !constantValue.IsBad) { TypeSymbol resultType = kind == BinaryOperatorKind.EnumSubtraction ? underlyingType : enumType; // We might need to convert back to the underlying type. return FoldConstantNumericConversion(syntax, constantValue, resultType, diagnostics); } return constantValue; }