public ResolveResult ResolveBinaryOperator(BinaryOperatorType op, ResolveResult lhs, ResolveResult rhs)
{
if (lhs.Type.Kind == TypeKind.Dynamic || rhs.Type.Kind == TypeKind.Dynamic) {
lhs = Convert(lhs, SpecialType.Dynamic);
rhs = Convert(rhs, SpecialType.Dynamic);
return BinaryOperatorResolveResult(SpecialType.Dynamic, lhs, op, rhs);
}
// C# 4.0 spec: ยง7.3.4 Binary operator overload resolution
string overloadableOperatorName = GetOverloadableOperatorName(op);
if (overloadableOperatorName == null) {
// Handle logical and/or exactly as bitwise and/or:
// - If the user overloads a bitwise operator, that implicitly creates the corresponding logical operator.
// - If both inputs are compile-time constants, it doesn't matter that we don't short-circuit.
// - If inputs aren't compile-time constants, we don't evaluate anything, so again it doesn't matter that we don't short-circuit
if (op == BinaryOperatorType.ConditionalAnd) {
overloadableOperatorName = GetOverloadableOperatorName(BinaryOperatorType.BitwiseAnd);
} else if (op == BinaryOperatorType.ConditionalOr) {
overloadableOperatorName = GetOverloadableOperatorName(BinaryOperatorType.BitwiseOr);
} else if (op == BinaryOperatorType.NullCoalescing) {
// null coalescing operator is not overloadable and needs to be handled separately
return ResolveNullCoalescingOperator(lhs, rhs);
} else {
return ErrorResolveResult.UnknownError;
}
}
// If the type is nullable, get the underlying type:
bool isNullable = NullableType.IsNullable(lhs.Type) || NullableType.IsNullable(rhs.Type);
IType lhsType = NullableType.GetUnderlyingType(lhs.Type);
IType rhsType = NullableType.GetUnderlyingType(rhs.Type);
// the operator is overloadable:
OverloadResolution userDefinedOperatorOR = CreateOverloadResolution(new[] { lhs, rhs });
HashSet<IParameterizedMember> userOperatorCandidates = new HashSet<IParameterizedMember>();
userOperatorCandidates.UnionWith(GetUserDefinedOperatorCandidates(lhsType, overloadableOperatorName));
userOperatorCandidates.UnionWith(GetUserDefinedOperatorCandidates(rhsType, overloadableOperatorName));
foreach (var candidate in userOperatorCandidates) {
userDefinedOperatorOR.AddCandidate(candidate);
}
if (userDefinedOperatorOR.FoundApplicableCandidate) {
return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow));
}
if (lhsType.Kind == TypeKind.Null && rhsType.IsReferenceType == false
|| lhsType.IsReferenceType == false && rhsType.Kind == TypeKind.Null)
{
isNullable = true;
}
if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight) {
// special case: the shift operators allow "var x = null << null", producing int?.
if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null)
isNullable = true;
// for shift operators, do unary promotion independently on both arguments
lhs = UnaryNumericPromotion(UnaryOperatorType.Plus, ref lhsType, isNullable, lhs);
rhs = UnaryNumericPromotion(UnaryOperatorType.Plus, ref rhsType, isNullable, rhs);
} else {
bool allowNullableConstants = op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality;
if (!BinaryNumericPromotion(isNullable, ref lhs, ref rhs, allowNullableConstants))
return new ErrorResolveResult(lhs.Type);
}
// re-read underlying types after numeric promotion
lhsType = NullableType.GetUnderlyingType(lhs.Type);
rhsType = NullableType.GetUnderlyingType(rhs.Type);
IEnumerable<CSharpOperators.OperatorMethod> methodGroup;
CSharpOperators operators = CSharpOperators.Get(compilation);
switch (op) {
case BinaryOperatorType.Multiply:
methodGroup = operators.MultiplicationOperators;
break;
case BinaryOperatorType.Divide:
methodGroup = operators.DivisionOperators;
break;
case BinaryOperatorType.Modulus:
methodGroup = operators.RemainderOperators;
break;
case BinaryOperatorType.Add:
methodGroup = operators.AdditionOperators;
{
if (lhsType.Kind == TypeKind.Enum) {
// E operator +(E x, U y);
IType underlyingType = MakeNullable(GetEnumUnderlyingType(lhsType), isNullable);
if (TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs)) {
return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs);
}
}
if (rhsType.Kind == TypeKind.Enum) {
// E operator +(U x, E y);
IType underlyingType = MakeNullable(GetEnumUnderlyingType(rhsType), isNullable);
if (TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs)) {
return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs);
}
}
if (lhsType.Kind == TypeKind.Delegate && TryConvert(ref rhs, lhsType)) {
return BinaryOperatorResolveResult(lhsType, lhs, op, rhs);
} else if (rhsType.Kind == TypeKind.Delegate && TryConvert(ref lhs, rhsType)) {
return BinaryOperatorResolveResult(rhsType, lhs, op, rhs);
}
if (lhsType is PointerType) {
methodGroup = new [] {
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int32),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt32),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int64),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt64)
};
} else if (rhsType is PointerType) {
methodGroup = new [] {
PointerArithmeticOperator(rhsType, KnownTypeCode.Int32, rhsType),
PointerArithmeticOperator(rhsType, KnownTypeCode.UInt32, rhsType),
PointerArithmeticOperator(rhsType, KnownTypeCode.Int64, rhsType),
PointerArithmeticOperator(rhsType, KnownTypeCode.UInt64, rhsType)
};
}
if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null)
return new ErrorResolveResult(SpecialType.NullType);
}
break;
case BinaryOperatorType.Subtract:
methodGroup = operators.SubtractionOperators;
{
if (lhsType.Kind == TypeKind.Enum) {
// U operator โ(E x, E y);
if (TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs, allowConversionFromConstantZero: false)) {
return HandleEnumSubtraction(isNullable, lhsType, lhs, rhs);
}
// E operator โ(E x, U y);
IType underlyingType = MakeNullable(GetEnumUnderlyingType(lhsType), isNullable);
if (TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs)) {
return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs);
}
}
if (rhsType.Kind == TypeKind.Enum) {
// U operator โ(E x, E y);
if (TryConvertEnum(ref lhs, rhs.Type, ref isNullable, ref rhs)) {
return HandleEnumSubtraction(isNullable, rhsType, lhs, rhs);
}
// E operator -(U x, E y);
IType underlyingType = MakeNullable(GetEnumUnderlyingType(rhsType), isNullable);
if (TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs)) {
return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs);
}
}
if (lhsType.Kind == TypeKind.Delegate && TryConvert(ref rhs, lhsType)) {
return BinaryOperatorResolveResult(lhsType, lhs, op, rhs);
} else if (rhsType.Kind == TypeKind.Delegate && TryConvert(ref lhs, rhsType)) {
return BinaryOperatorResolveResult(rhsType, lhs, op, rhs);
}
if (lhsType is PointerType) {
if (rhsType is PointerType) {
IType int64 = compilation.FindType(KnownTypeCode.Int64);
if (lhsType.Equals(rhsType)) {
return BinaryOperatorResolveResult(int64, lhs, op, rhs);
} else {
return new ErrorResolveResult(int64);
}
}
methodGroup = new [] {
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int32),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt32),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int64),
PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt64)
};
}
if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null)
return new ErrorResolveResult(SpecialType.NullType);
}
break;
case BinaryOperatorType.ShiftLeft:
methodGroup = operators.ShiftLeftOperators;
break;
case BinaryOperatorType.ShiftRight:
methodGroup = operators.ShiftRightOperators;
break;
case BinaryOperatorType.Equality:
case BinaryOperatorType.InEquality:
case BinaryOperatorType.LessThan:
case BinaryOperatorType.GreaterThan:
case BinaryOperatorType.LessThanOrEqual:
case BinaryOperatorType.GreaterThanOrEqual:
{
if (lhsType.Kind == TypeKind.Enum && TryConvert(ref rhs, lhs.Type)) {
// bool operator op(E x, E y);
return HandleEnumComparison(op, lhsType, isNullable, lhs, rhs);
} else if (rhsType.Kind == TypeKind.Enum && TryConvert(ref lhs, rhs.Type)) {
// bool operator op(E x, E y);
return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs);
} else if (lhsType is PointerType && rhsType is PointerType) {
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs);
}
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) {
if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) {
// If it's a reference comparison
if (op == BinaryOperatorType.Equality)
methodGroup = operators.ReferenceEqualityOperators;
else
methodGroup = operators.ReferenceInequalityOperators;
break;
} else if (lhsType.Kind == TypeKind.Null && IsNullableTypeOrNonValueType(rhs.Type)
|| IsNullableTypeOrNonValueType(lhs.Type) && rhsType.Kind == TypeKind.Null) {
// compare type parameter or nullable type with the null literal
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs);
}
}
switch (op) {
case BinaryOperatorType.Equality:
methodGroup = operators.ValueEqualityOperators;
break;
case BinaryOperatorType.InEquality:
methodGroup = operators.ValueInequalityOperators;
break;
case BinaryOperatorType.LessThan:
methodGroup = operators.LessThanOperators;
break;
case BinaryOperatorType.GreaterThan:
methodGroup = operators.GreaterThanOperators;
break;
case BinaryOperatorType.LessThanOrEqual:
methodGroup = operators.LessThanOrEqualOperators;
break;
case BinaryOperatorType.GreaterThanOrEqual:
methodGroup = operators.GreaterThanOrEqualOperators;
break;
default:
throw new InvalidOperationException();
}
}
break;
case BinaryOperatorType.BitwiseAnd:
case BinaryOperatorType.BitwiseOr:
case BinaryOperatorType.ExclusiveOr:
{
if (lhsType.Kind == TypeKind.Enum) {
// bool operator op(E x, E y);
if (TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs)) {
return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs);
}
}
if (rhsType.Kind == TypeKind.Enum) {
// bool operator op(E x, E y);
if (TryConvertEnum (ref lhs, rhs.Type, ref isNullable, ref rhs)) {
return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs);
}
}
switch (op) {
case BinaryOperatorType.BitwiseAnd:
methodGroup = operators.BitwiseAndOperators;
break;
case BinaryOperatorType.BitwiseOr:
methodGroup = operators.BitwiseOrOperators;
break;
case BinaryOperatorType.ExclusiveOr:
methodGroup = operators.BitwiseXorOperators;
break;
default:
throw new InvalidOperationException();
}
}
break;
case BinaryOperatorType.ConditionalAnd:
methodGroup = operators.LogicalAndOperators;
break;
case BinaryOperatorType.ConditionalOr:
methodGroup = operators.LogicalOrOperators;
break;
default:
throw new InvalidOperationException();
}
OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { lhs, rhs });
foreach (var candidate in methodGroup) {
builtinOperatorOR.AddCandidate(candidate);
}
CSharpOperators.BinaryOperatorMethod m = (CSharpOperators.BinaryOperatorMethod)builtinOperatorOR.BestCandidate;
IType resultType = m.ReturnType;
if (builtinOperatorOR.BestCandidateErrors != OverloadResolutionErrors.None) {
// If there are any user-defined operators, prefer those over the built-in operators.
// It'll be a more informative error.
if (userDefinedOperatorOR.BestCandidate != null)
return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow));
else
return new ErrorResolveResult(resultType);
} else if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && m.CanEvaluateAtCompileTime) {
object val;
try {
val = m.Invoke(this, lhs.ConstantValue, rhs.ConstantValue);
} catch (ArithmeticException) {
return new ErrorResolveResult(resultType);
}
return new ConstantResolveResult(resultType, val);
} else {
lhs = Convert(lhs, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]);
rhs = Convert(rhs, m.Parameters[1].Type, builtinOperatorOR.ArgumentConversions[1]);
return BinaryOperatorResolveResult(resultType, lhs, op, rhs,
builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator);
}
}