private BinaryOperatorAnalysisResult(OperatorAnalysisResultKind kind, BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion) { this.Kind = kind; this.Signature = signature; this.LeftConversion = leftConversion; this.RightConversion = rightConversion; }
public bool Equals(BinaryOperatorSignature other) { return (this.Kind == other.Kind && this.LeftType.Equals(other.LeftType) && this.RightType.Equals(other.RightType) && this.ReturnType.Equals(other.ReturnType) && this.Method.Equals(other.Method)); }
public bool Equals(BinaryOperatorSignature other) { return this.Kind == other.Kind && this.LeftType.Equals(other.LeftType) && this.RightType.Equals(other.RightType) && this.ReturnType.Equals(other.ReturnType) && this.Method == other.Method; }
public static BinaryOperatorAnalysisResult Inapplicable( BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion ) { return(new BinaryOperatorAnalysisResult( OperatorAnalysisResultKind.Inapplicable, signature, leftConversion, rightConversion )); }
private void BinaryOperatorEasyOut(BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BinaryOperatorOverloadResolutionResult result) { var leftType = left.Type; if (leftType is null) { return; } var rightType = right.Type; if (rightType is null) { return; } if (PossiblyUnusualConstantOperation(left, right)) { return; } var easyOut = BinopEasyOut.OpKind(kind, leftType, rightType); if (easyOut == BinaryOperatorKind.Error) { return; } BinaryOperatorSignature signature = this.Compilation.builtInOperators.GetSignature(easyOut); Conversion leftConversion = Conversions.FastClassifyConversion(leftType, signature.LeftType); Conversion rightConversion = Conversions.FastClassifyConversion(rightType, signature.RightType); Debug.Assert(leftConversion.Exists && leftConversion.IsImplicit); Debug.Assert(rightConversion.Exists && rightConversion.IsImplicit); result.Results.Add(BinaryOperatorAnalysisResult.Applicable(signature, leftConversion, rightConversion)); }
public BoundCompoundAssignmentOperator( SyntaxNode syntax, BinaryOperatorSignature @operator, BoundExpression left, BoundExpression right, Conversion leftConversion, Conversion finalConversion, LookupResultKind resultKind, ImmutableArray <MethodSymbol> originalUserDefinedOperatorsOpt, TypeSymbol type, bool hasErrors = false) : this( syntax, @operator, left, right, leftConversion, finalConversion, resultKind, type, hasErrors) { this.OriginalUserDefinedOperatorsOpt = originalUserDefinedOperatorsOpt; }
private bool IsValidUserDefinedConditionalLogicalOperator( CSharpSyntaxNode syntax, BinaryOperatorSignature signature, DiagnosticBag diagnostics, out MethodSymbol trueOperator, out MethodSymbol falseOperator) { Debug.Assert(signature.Kind.OperandTypes() == BinaryOperatorKind.UserDefined); // SPEC: When the operands of && or || are of types that declare an applicable // SPEC: user-defined operator & or |, both of the following must be true, where // SPEC: T is the type in which the selected operator is defined: // SPEC VIOLATION: // // The native compiler violates the specification, the native compiler allows: // // public static D? operator &(D? d1, D? d2) { ... } // public static bool operator true(D? d) { ... } // public static bool operator false(D? d) { ... } // // to be used as D? && D? or D? || D?. But if you do this: // // public static D operator &(D d1, D d2) { ... } // public static bool operator true(D? d) { ... } // public static bool operator false(D? d) { ... } // // And use the *lifted* form of the operator, this is disallowed. // // public static D? operator &(D? d1, D d2) { ... } // public static bool operator true(D? d) { ... } // public static bool operator false(D? d) { ... } // // Is not allowed because "the return type must be the same as the type of both operands" // which is not at all what the spec says. // // We ought not to break backwards compatibility with the native compiler. The spec // is plausibly in error; it is possible that this section of the specification was // never updated when nullable types and lifted operators were added to the language. // And it seems like the native compiler's behavior of allowing a nullable // version but not a lifted version is a bug that should be fixed. // // Therefore we will do the following in Roslyn: // // * The return and parameter types of the chosen operator, whether lifted or unlifted, // must be the same. // * The return and parameter types must be either the enclosing type, or its corresponding // nullable type. // * There must be an operator true/operator false that takes the left hand type of the operator. // Only classes and structs contain user-defined operators, so we know it is a named type symbol. NamedTypeSymbol t = (NamedTypeSymbol)signature.Method.ContainingType; // SPEC: The return type and the type of each parameter of the selected operator // SPEC: must be T. // As mentioned above, we relax this restriction. The types must all be the same. bool typesAreSame = signature.LeftType == signature.RightType && signature.LeftType == signature.ReturnType; bool typeMatchesContainer = signature.ReturnType == t || signature.ReturnType.IsNullableType() && signature.ReturnType.GetNullableUnderlyingType() == t; if (!typesAreSame || !typeMatchesContainer) { // CS0217: In order to be applicable as a short circuit operator a user-defined logical // operator ('{0}') must have the same return type and parameter types Error(diagnostics, ErrorCode.ERR_BadBoolOp, syntax, signature.Method); trueOperator = null; falseOperator = null; return false; } // SPEC: T must contain declarations of operator true and operator false. // As mentioned above, we need more than just op true and op false existing; we need // to know that the first operand can be passed to it. HashSet<DiagnosticInfo> useSiteDiagnostics = null; if (!HasApplicableBooleanOperator(t, WellKnownMemberNames.TrueOperatorName, signature.LeftType, ref useSiteDiagnostics, out trueOperator) || !HasApplicableBooleanOperator(t, WellKnownMemberNames.FalseOperatorName, signature.LeftType, ref useSiteDiagnostics, out falseOperator)) { // I have changed the wording of this error message. The original wording was: // CS0218: The type ('T') must contain declarations of operator true and operator false // I have changed that to: // CS0218: In order to be applicable as a short circuit operator, the declaring type // '{1}' of user-defined operator '{0}' must declare operator true and operator false. Error(diagnostics, ErrorCode.ERR_MustHaveOpTF, syntax, signature.Method, t); diagnostics.Add(syntax, useSiteDiagnostics); trueOperator = null; falseOperator = null; return false; } diagnostics.Add(syntax, useSiteDiagnostics); // For the remainder of this method the comments WOLOG assume that we're analyzing an &&. The // exact same issues apply to ||. // Note that the mere *existence* of operator true and operator false is sufficient. They // are already constrained to take either T or T?. Since we know that the applicable // T.& takes (T, T), we know that both sides of the && are implicitly convertible // to T, and therefore the left side is implicitly convertible to T or T?. // SPEC: The expression x && y is evaluated as T.false(x) ? x : T.&(x,y) ... except that // SPEC: x is only evaluated once. // // DELIBERATE SPEC VIOLATION: The native compiler does not actually evaluate x&&y in this // manner. Suppose X is of type X. The code above is equivalent to: // // X temp = x, then evaluate: // T.false(temp) ? temp : T.&(temp, y) // // What the native compiler actually evaluates is: // // T temp = x, then evaluate // T.false(temp) ? temp : T.&(temp, y) // // That is a small difference but it has an observable effect. For example: // // class V { public static implicit operator T(V v) { ... } } // class X : V { public static implicit operator T?(X x) { ... } } // struct T { // public static operator false(T? t) { ... } // public static operator true(T? t) { ... } // public static T operator &(T t1, T t2) { ... } // } // // Under the spec'd interpretation, if we had x of type X and y of type T then x && y is // // X temp = x; // T.false(temp) ? temp : T.&(temp, y) // // which would then be analyzed as: // // T.false(X.op_Implicit_To_Nullable_T(temp)) ? // V.op_Implicit_To_T(temp) : // T.&(op_Implicit_To_T(temp), y) // // But the native compiler actually generates: // // T temp = V.Op_Implicit_To_T(x); // T.false(new T?(temp)) ? temp : T.&(temp, y) // // That is, the native compiler converts the temporary to the type of the declaring operator type // regardless of the fact that there is a better conversion for the T.false call. // // We choose to match the native compiler behavior here; we might consider fixing // the spec to match the compiler. // // With this decision we need not keep track of any extra information in the bound // binary operator node; we need to know the left hand side converted to T, the right // hand side converted to T, and the method symbol of the chosen T.&(T, T) method. // The rewriting pass has enough information to deduce which T.false is to be called, // and can convert the T to T? if necessary. return true; }
private BoundExpression BindCompoundAssignment(AssignmentExpressionSyntax node, DiagnosticBag diagnostics) { BoundExpression left = BindValue(node.Left, diagnostics, GetBinaryAssignmentKind(node.Kind())); BoundExpression right = BindValue(node.Right, diagnostics, BindValueKind.RValue); BinaryOperatorKind kind = SyntaxKindToBinaryOperatorKind(node.Kind()); // If either operand is bad, don't try to do binary operator overload resolution; that will just // make cascading errors. if (left.Kind == BoundKind.EventAccess) { BinaryOperatorKind kindOperator = kind.Operator(); switch (kindOperator) { case BinaryOperatorKind.Addition: case BinaryOperatorKind.Subtraction: return BindEventAssignment(node, (BoundEventAccess)left, right, kindOperator, diagnostics); // fall-through for other operators, if RHS is dynamic we produce dynamic operation, otherwise we'll report an error ... } } if (left.HasAnyErrors || right.HasAnyErrors) { // NOTE: no overload resolution candidates. return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, LookupResultKind.Empty, CreateErrorType(), hasErrors: true); } HashSet<DiagnosticInfo> useSiteDiagnostics = null; if (left.HasDynamicType() || right.HasDynamicType()) { if (IsLegalDynamicOperand(right) && IsLegalDynamicOperand(left)) { var finalDynamicConversion = this.Compilation.Conversions.ClassifyConversionFromExpression(right, left.Type, ref useSiteDiagnostics); diagnostics.Add(node, useSiteDiagnostics); return new BoundCompoundAssignmentOperator( node, new BinaryOperatorSignature( kind.WithType(BinaryOperatorKind.Dynamic).WithOverflowChecksIfApplicable(CheckOverflowAtRuntime), left.Type, right.Type, Compilation.DynamicType), left, right, Conversion.NoConversion, finalDynamicConversion, LookupResultKind.Viable, left.Type, hasErrors: false); } else { Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display); // error: operator can't be applied on dynamic and a type that is not convertible to dynamic: return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, LookupResultKind.Empty, CreateErrorType(), hasErrors: true); } } if (left.Kind == BoundKind.EventAccess && !CheckEventValueKind((BoundEventAccess)left, BindValueKind.Assignment, diagnostics)) { // If we're in a place where the event can be assigned, then continue so that we give errors // about the types and operator not lining up. Otherwise, just report that the event can't // be used here. // NOTE: no overload resolution candidates. return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, LookupResultKind.NotAVariable, CreateErrorType(), hasErrors: true); } // A compound operator, say, x |= y, is bound as x = (X)( ((T)x) | ((T)y) ). We must determine // the binary operator kind, the type conversions from each side to the types expected by // the operator, and the type conversion from the return type of the operand to the left hand side. // // We can get away with binding the right-hand-side of the operand into its converted form early. // This is convenient because first, it is never rewritten into an access to a temporary before // the conversion, and second, because that is more convenient for the "d += lambda" case. // We want to have the converted (bound) lambda in the bound tree, not the unconverted unbound lambda. LookupResultKind resultKind; ImmutableArray<MethodSymbol> originalUserDefinedOperators; BinaryOperatorAnalysisResult best = this.BinaryOperatorOverloadResolution(kind, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators); if (!best.HasValue) { ReportAssignmentOperatorError(node, diagnostics, left, right, resultKind); return new BoundCompoundAssignmentOperator(node, BinaryOperatorSignature.Error, left, right, Conversion.NoConversion, Conversion.NoConversion, resultKind, originalUserDefinedOperators, CreateErrorType(), hasErrors: true); } // The rules in the spec for determining additional errors are bit confusing. In particular // this line is misleading: // // "for predefined operators ... x op= y is permitted if both x op y and x = y are permitted" // // That's not accurate in many cases. For example, "x += 1" is permitted if x is string or // any enum type, but x = 1 is not legal for strings or enums. // // The correct rules are spelled out in the spec: // // Spec §7.17.2: // An operation of the form x op= y is processed by applying binary operator overload // resolution (§7.3.4) as if the operation was written x op y. // Let R be the return type of the selected operator, and T the type of x. Then, // // * If an implicit conversion from an expression of type R to the type T exists, // the operation is evaluated as x = (T)(x op y), except that x is evaluated only once. // [no cast is inserted, unless the conversion is implicit dynamic] // * Otherwise, if // (1) the selected operator is a predefined operator, // (2) if R is explicitly convertible to T, and // (3.1) if y is implicitly convertible to T or // (3.2) the operator is a shift operator... [then cast the result to T] // * Otherwise ... a binding-time error occurs. // So let's tease that out. There are two possible errors: the conversion from the // operator result type to the left hand type could be bad, and the conversion // from the right hand side to the left hand type could be bad. // // We report the first error under the following circumstances: // // * The final conversion is bad, or // * The final conversion is explicit and the selected operator is not predefined // // We report the second error under the following circumstances: // // * The final conversion is explicit, and // * The selected operator is predefined, and // * the selected operator is not a shift, and // * the right-to-left conversion is not implicit bool hasError = false; BinaryOperatorSignature bestSignature = best.Signature; if (CheckOverflowAtRuntime) { bestSignature = new BinaryOperatorSignature( bestSignature.Kind.WithOverflowChecksIfApplicable(CheckOverflowAtRuntime), bestSignature.LeftType, bestSignature.RightType, bestSignature.ReturnType, bestSignature.Method); } var leftType = left.Type; Conversion finalConversion = Conversions.ClassifyConversionFromType(bestSignature.ReturnType, leftType, ref useSiteDiagnostics); BoundExpression rightConverted = CreateConversion(right, best.RightConversion, bestSignature.RightType, diagnostics); bool isPredefinedOperator = !bestSignature.Kind.IsUserDefined(); if (!finalConversion.IsValid || finalConversion.IsExplicit && !isPredefinedOperator) { hasError = true; GenerateImplicitConversionError(diagnostics, this.Compilation, node, finalConversion, bestSignature.ReturnType, leftType); } else { ReportDiagnosticsIfObsolete(diagnostics, finalConversion, node, hasBaseReceiver: false); } if (finalConversion.IsExplicit && isPredefinedOperator && !kind.IsShift()) { Conversion rightToLeftConversion = this.Conversions.ClassifyConversionFromExpression(right, leftType, ref useSiteDiagnostics); if (!rightToLeftConversion.IsImplicit || !rightToLeftConversion.IsValid) { hasError = true; GenerateImplicitConversionError(diagnostics, node, rightToLeftConversion, right, leftType); } } diagnostics.Add(node, useSiteDiagnostics); if (!hasError && leftType.IsVoidPointer()) { Error(diagnostics, ErrorCode.ERR_VoidError, node); hasError = true; } // Any events that weren't handled above (by BindEventAssignment) are bad - we just followed this // code path for the diagnostics. Make sure we don't report success. Debug.Assert(left.Kind != BoundKind.EventAccess || hasError); Conversion leftConversion = best.LeftConversion; ReportDiagnosticsIfObsolete(diagnostics, leftConversion, node, hasBaseReceiver: false); return new BoundCompoundAssignmentOperator(node, bestSignature, left, rightConverted, leftConversion, finalConversion, resultKind, originalUserDefinedOperators, leftType, hasError); }
private BetterResult MoreSpecificOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { TypeSymbol op1Left, op1Right, op2Left, op2Right; if ((object)op1.Method != null) { var p = op1.Method.OriginalDefinition.GetParameters(); op1Left = p[0].Type; op1Right = p[1].Type; if (op1.Kind.IsLifted()) { op1Left = MakeNullable(op1Left); op1Right = MakeNullable(op1Right); } } else { op1Left = op1.LeftType; op1Right = op1.RightType; } if ((object)op2.Method != null) { var p = op2.Method.OriginalDefinition.GetParameters(); op2Left = p[0].Type; op2Right = p[1].Type; if (op2.Kind.IsLifted()) { op2Left = MakeNullable(op2Left); op2Right = MakeNullable(op2Right); } } else { op2Left = op2.LeftType; op2Right = op2.RightType; } var uninst1 = ArrayBuilder<TypeSymbol>.GetInstance(); var uninst2 = ArrayBuilder<TypeSymbol>.GetInstance(); uninst1.Add(op1Left); uninst1.Add(op1Right); uninst2.Add(op2Left); uninst2.Add(op2Right); BetterResult result = MoreSpecificType(uninst1, uninst2, ref useSiteDiagnostics); uninst1.Free(); uninst2.Free(); return result; }
private BetterResult BetterOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, BoundExpression left, BoundExpression right, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { Debug.Assert(op1.Priority.HasValue == op2.Priority.HasValue); // We use Priority as a tie-breaker to help match native compiler bugs. if (op1.Priority.HasValue && op2.Priority.HasValue && op1.Priority.GetValueOrDefault() != op2.Priority.GetValueOrDefault()) { return (op1.Priority.GetValueOrDefault() < op2.Priority.GetValueOrDefault()) ? BetterResult.Left : BetterResult.Right; } BetterResult leftBetter = BetterConversionFromExpression(left, op1.LeftType, op2.LeftType, ref useSiteDiagnostics); BetterResult rightBetter = BetterConversionFromExpression(right, op1.RightType, op2.RightType, ref useSiteDiagnostics); // SPEC: Mp is defined to be a better function member than Mq if: // SPEC: * For each argument, the implicit conversion from Ex to Qx is not better than // SPEC: the implicit conversion from Ex to Px, and // SPEC: * For at least one argument, the conversion from Ex to Px is better than the // SPEC: conversion from Ex to Qx. // If that is hard to follow, consult this handy chart: // op1.Left vs op2.Left op1.Right vs op2.Right result // ----------------------------------------------------------- // op1 better op1 better op1 better // op1 better neither better op1 better // op1 better op2 better neither better // neither better op1 better op1 better // neither better neither better neither better // neither better op2 better op2 better // op2 better op1 better neither better // op2 better neither better op2 better // op2 better op2 better op2 better if (leftBetter == BetterResult.Left && rightBetter != BetterResult.Right || leftBetter != BetterResult.Right && rightBetter == BetterResult.Left) { return BetterResult.Left; } if (leftBetter == BetterResult.Right && rightBetter != BetterResult.Left || leftBetter != BetterResult.Left && rightBetter == BetterResult.Right) { return BetterResult.Right; } // There was no better member on the basis of conversions. Go to the tiebreaking round. // SPEC: In case the parameter type sequences P1, P2 and Q1, Q2 are equivalent -- that is, every Pi // SPEC: has an identity conversion to the corresponding Qi -- the following tie-breaking rules // SPEC: are applied: if (Conversions.HasIdentityConversion(op1.LeftType, op2.LeftType) && Conversions.HasIdentityConversion(op1.RightType, op2.RightType)) { // NOTE: The native compiler does not follow these rules; effectively, the native // compiler checks for liftedness first, and then for specificity. For example: // struct S<T> where T : struct { // public static bool operator +(S<T> x, int y) { return true; } // public static bool? operator +(S<T>? x, int? y) { return false; } // } // // bool? b = new S<int>?() + new int?(); // // should reason as follows: the two applicable operators are the lifted // form of the first operator and the unlifted second operator. The // lifted form of the first operator is *more specific* because int? // is more specific than T?. Therefore it should win. In fact the // native compiler chooses the second operator, because it is unlifted. // // Roslyn follows the spec rules; if we decide to change the spec to match // the native compiler, or decide to change Roslyn to match the native // compiler, we should change the order of the checks here. // SPEC: If Mp has more specific parameter types than Mq then Mp is better than Mq. BetterResult result = MoreSpecificOperator(op1, op2, ref useSiteDiagnostics); if (result == BetterResult.Left || result == BetterResult.Right) { return result; } // SPEC: If one member is a non-lifted operator and the other is a lifted operator, // SPEC: the non-lifted one is better. bool lifted1 = op1.Kind.IsLifted(); bool lifted2 = op2.Kind.IsLifted(); if (lifted1 && !lifted2) { return BetterResult.Right; } else if (!lifted1 && lifted2) { return BetterResult.Left; } } return BetterResult.Neither; }
public BoundCompoundAssignmentOperator( CSharpSyntaxNode syntax, BinaryOperatorSignature @operator, BoundExpression left, BoundExpression right, Conversion leftConversion, Conversion finalConversion, LookupResultKind resultKind, ImmutableArray<MethodSymbol> originalUserDefinedOperatorsOpt, TypeSymbol type, bool hasErrors = false) : this( syntax, @operator, left, right, leftConversion, finalConversion, resultKind, type, hasErrors) { this.OriginalUserDefinedOperatorsOpt = originalUserDefinedOperatorsOpt; }
public static BinaryOperatorAnalysisResult Inapplicable(BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion) { return new BinaryOperatorAnalysisResult(OperatorAnalysisResultKind.Inapplicable, signature, leftConversion, rightConversion); }
private bool IsApplicable(BinaryOperatorSignature binaryOperator, BoundExpression left, BoundExpression right, ref HashSet<DiagnosticInfo> useSiteDiagnostics) { return Conversions.ClassifyImplicitConversionFromExpression(left, binaryOperator.LeftType, ref useSiteDiagnostics).Exists && Conversions.ClassifyImplicitConversionFromExpression(right, binaryOperator.RightType, ref useSiteDiagnostics).Exists; }
private BetterResult VoBetterOperator(BinaryOperatorSignature op1, BinaryOperatorSignature op2, BoundExpression left, BoundExpression right, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { // When the binary operators are equal we inspect the types if ((op1.Kind & BinaryOperatorKind.OpMask) == (op2.Kind & BinaryOperatorKind.OpMask)) { if ((op1.Kind & BinaryOperatorKind.TypeMask) == BinaryOperatorKind.Float && (op2.Kind & BinaryOperatorKind.TypeMask) == BinaryOperatorKind.Double) { // Lhs = real4, rhs = real8, choose real8 return(BetterResult.Right); } if ((op1.Kind & BinaryOperatorKind.TypeMask) == BinaryOperatorKind.Double) { // rhs = numeric, lhs = double choose double switch (op2.Kind & BinaryOperatorKind.TypeMask) { case BinaryOperatorKind.Int: case BinaryOperatorKind.UInt: case BinaryOperatorKind.Long: case BinaryOperatorKind.ULong: case BinaryOperatorKind.Float: case BinaryOperatorKind.Decimal: return(BetterResult.Left); } } if ((op2.Kind & BinaryOperatorKind.TypeMask) == BinaryOperatorKind.Double) { // lhs = numeric, rhs = double choose double switch (op1.Kind & BinaryOperatorKind.TypeMask) { case BinaryOperatorKind.Int: case BinaryOperatorKind.UInt: case BinaryOperatorKind.Long: case BinaryOperatorKind.ULong: case BinaryOperatorKind.Float: case BinaryOperatorKind.Decimal: return(BetterResult.Right); } } if (left.Type != null && right.Type != null) { bool enumL = left.Type.IsEnumType() || left.Type.IsNullableType() && left.Type.GetNullableUnderlyingType().IsEnumType(); bool enumR = right.Type.IsEnumType() || right.Type.IsNullableType() && right.Type.GetNullableUnderlyingType().IsEnumType(); if (enumL ^ enumR) { bool enum1 = (op1.LeftType.IsEnumType() || op1.LeftType.IsNullableType() && op1.LeftType.GetNullableUnderlyingType().IsEnumType()) && (op1.RightType.IsEnumType() || op1.RightType.IsNullableType() && op1.RightType.GetNullableUnderlyingType().IsEnumType()); bool enum2 = (op2.LeftType.IsEnumType() || op2.LeftType.IsNullableType() && op2.LeftType.GetNullableUnderlyingType().IsEnumType()) && (op2.RightType.IsEnumType() || op2.RightType.IsNullableType() && op2.RightType.GetNullableUnderlyingType().IsEnumType()); if (enum1 && !enum2) { return(BetterResult.Left); } else if (!enum1 && enum2) { return(BetterResult.Right); } } // when /vo4 is enabled then we may end up having duplicate candidates // we decide here which one takes precedence if (Compilation.Options.HasOption(CompilerOption.SignedUnsignedConversion, left.Syntax)) { #region Integral Binary Operators if (left.Type.IsIntegralType() && right.Type.IsIntegralType() && op1.Kind.IsIntegral() && op2.Kind.IsIntegral()) { // when both operands have integral types, choose the one that match the sign and or size // we check the lhs of the expression first bool exprSigned = left.Type.SpecialType.IsSignedIntegralType(); bool op1Signed = op1.LeftType.SpecialType.IsSignedIntegralType(); bool op2Signed = op2.LeftType.SpecialType.IsSignedIntegralType(); int exprSize = left.Type.SpecialType.SizeInBytes(); int op1Size = op1.LeftType.SpecialType.SizeInBytes(); int op2Size = op2.LeftType.SpecialType.SizeInBytes(); // op1 matches sign and size and op2 does not if ((exprSigned == op1Signed && exprSize == op1Size) && (exprSigned != op2Signed || exprSize != op2Size)) { return(BetterResult.Left); } // op2 matches sign and size and op1 does not if ((exprSigned != op1Signed || exprSize != op1Size) && (exprSigned == op2Signed && exprSize == op2Size)) { return(BetterResult.Right); } // When we get here they both match or both do not match the sign and size // now check the rhs of the expression, to see if this helps to decide exprSigned = right.Type.SpecialType.IsSignedIntegralType(); exprSize = right.Type.SpecialType.SizeInBytes(); op1Signed = op1.RightType.SpecialType.IsSignedIntegralType(); op2Signed = op2.RightType.SpecialType.IsSignedIntegralType(); // when still undecided then choose the one where the size matches best // op1 matches sign and size and op2 does not if ((exprSigned == op1Signed && exprSize == op1Size) && (exprSigned != op2Signed || exprSize != op2Size)) { return(BetterResult.Left); } // op2 matches sign and size and op1 does not if ((exprSigned != op1Signed || exprSize != op1Size) && (exprSigned == op2Signed && exprSize == op2Size)) { return(BetterResult.Right); } // still no match. Forget the size and check only on sign exprSigned = left.Type.SpecialType.IsSignedIntegralType(); op1Signed = op1.LeftType.SpecialType.IsSignedIntegralType(); op2Signed = op2.LeftType.SpecialType.IsSignedIntegralType(); // op1 matches sign and op2 does not if (exprSigned == op1Signed && exprSigned != op2Signed) { return(BetterResult.Left); } // op2 matches sign and op1 does not if (exprSigned != op1Signed && exprSigned == op2Signed) { return(BetterResult.Right); } exprSigned = right.Type.SpecialType.IsSignedIntegralType(); op1Signed = op1.RightType.SpecialType.IsSignedIntegralType(); op2Signed = op2.RightType.SpecialType.IsSignedIntegralType(); // op1 matches sign and op2 does not if (exprSigned == op1Signed && exprSigned != op2Signed) { return(BetterResult.Left); } // op2 matches sign and op1 does not if (exprSigned != op1Signed && exprSigned == op2Signed) { return(BetterResult.Right); } } #endregion } if ((left.Type.IsIntegralType() && right.Type.IsPointerType()) || left.Type.IsPointerType() && right.Type.IsIntegralType()) { if (op1.LeftType.IsVoidPointer() && op1.RightType.IsVoidPointer()) { return(BetterResult.Left); } if (op2.LeftType.IsVoidPointer() && op2.RightType.IsVoidPointer()) { return(BetterResult.Right); } } // Prefer Date over DateTime, becuse when one of the two is date then we know that we can't compare the time parts if (left.Type.SpecialType == SpecialType.System_DateTime && right.Type == Compilation.DateType()) { return(BetterResult.Right); } if (right.Type.SpecialType == SpecialType.System_DateTime && left.Type == Compilation.DateType()) { return(BetterResult.Left); } } } // Solve Literal operations such as generated by ForNext statement if (right.Kind == BoundKind.Literal && op1.LeftType == left.Type) { if (left.Type.SpecialType.IsSignedIntegralType()) // When signed, always Ok { return(BetterResult.Left); } else if (left.Type.SpecialType.IsIntegralType()) // Unsigned integral, so check for overflow { var constValue = ((BoundLiteral)right).ConstantValue; if (constValue.IsIntegral && constValue.Int64Value >= 0) { return(BetterResult.Left); } } else // not integral, so most likely floating point { return(BetterResult.Left); } } if (left.Kind == BoundKind.Literal && op1.RightType == right.Type) { if (right.Type.SpecialType.IsSignedIntegralType()) // When signed, always Ok { return(BetterResult.Left); } else if (right.Type.SpecialType.IsIntegralType()) // Unsigned integral, so check for overflow { var constValue = ((BoundLiteral)left).ConstantValue; if (constValue.IsIntegral && constValue.Int64Value >= 0) { return(BetterResult.Left); } } else // not integral, so most likely floating point { return(BetterResult.Left); } } if (Compilation.Options.HasRuntime) { var usualType = Compilation.UsualType(); if (left.Type != usualType) { if (op1.RightType != usualType && op2.RightType == usualType) { return(BetterResult.Left); } if (op2.RightType != usualType && op1.RightType == usualType) { return(BetterResult.Right); } } if (right.Type != usualType) { if (op1.LeftType != usualType && op2.LeftType == usualType) { return(BetterResult.Left); } if (op2.LeftType != usualType && op1.LeftType == usualType) { return(BetterResult.Right); } } } return(BetterResult.Neither); }