private BinaryOperatorAnalysisResult(OperatorAnalysisResultKind kind, BinaryOperatorSignature signature, Conversion leftConversion, Conversion rightConversion)
 {
     this.Kind = kind;
     this.Signature = signature;
     this.LeftConversion = leftConversion;
     this.RightConversion = rightConversion;
 }
示例#2
0
 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;
 }
示例#5
0
 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));
        }
示例#7
0
 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;
 }
示例#8
0
        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;
        }
示例#9
0
        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;
        }
示例#12
0
 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);
        }