Esempio n. 1
0
        public Expression ResolveBinaryOperator(ResolveContext rc, BinaryOperatorType op, Expression lhs, Expression rhs)
        {
            // V# 4.0 spec: §7.3.4 Binary operator overload resolution
            string overloadableOperatorName = ResolveContext.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.LogicalAnd)
                {
                    overloadableOperatorName = ResolveContext.GetOverloadableOperatorName(BinaryOperatorType.BitwiseAnd);
                }
                else if (op == BinaryOperatorType.LogicalOr)
                {
                    overloadableOperatorName = ResolveContext.GetOverloadableOperatorName(BinaryOperatorType.BitwiseOr);
                }
                else if (op == BinaryOperatorType.NullCoalescing)
                {
                    // null coalescing operator is not overloadable and needs to be handled separately
                    return(ResolveNullCoalescingOperator(rc, lhs, rhs));
                }
                else
                {
                    return(ErrorExpression.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  = rc.CreateOverloadResolution(new[] { lhs, rhs });
            HashSet <IParameterizedMember> userOperatorCandidates = new HashSet <IParameterizedMember>();

            userOperatorCandidates.UnionWith(rc.GetUserDefinedOperatorCandidates(lhsType, overloadableOperatorName));
            userOperatorCandidates.UnionWith(rc.GetUserDefinedOperatorCandidates(rhsType, overloadableOperatorName));
            foreach (var candidate in userOperatorCandidates)
            {
                userDefinedOperatorOR.AddCandidate(candidate);
            }
            if (userDefinedOperatorOR.FoundApplicableCandidate)
            {
                return(SetUserDefinedOperationInformations(rc, userDefinedOperatorOR));
            }

            if (lhsType.Kind == TypeKind.Null && rhsType.IsReferenceType == false ||
                lhsType.IsReferenceType == false && rhsType.Kind == TypeKind.Null)
            {
                isNullable = true;
            }
            if (op == BinaryOperatorType.LeftShift || op == BinaryOperatorType.RightShift)
            {
                // 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(rc, UnaryOperatorType.UnaryPlus, ref lhsType, isNullable, lhs);
                rhs = UnaryNumericPromotion(rc, UnaryOperatorType.UnaryPlus, ref rhsType, isNullable, rhs);
            }
            else
            {
                bool allowNullableConstants = op == BinaryOperatorType.Equality || op == BinaryOperatorType.Inequality;
                if (!BinaryNumericPromotion(rc, isNullable, ref lhs, ref rhs, allowNullableConstants))
                {
                    return(new ErrorExpression(lhs.Type));
                }
            }
            // re-read underlying types after numeric promotion
            lhsType = NullableType.GetUnderlyingType(lhs.Type);
            rhsType = NullableType.GetUnderlyingType(rhs.Type);

            IEnumerable <VSharpOperators.OperatorMethod> methodGroup;
            VSharpOperators operators = VSharpOperators.Get(rc.compilation);

            switch (op)
            {
            case BinaryOperatorType.Multiply:
                methodGroup = operators.MultiplicationOperators;
                break;

            case BinaryOperatorType.Division:
                methodGroup = operators.DivisionOperators;
                break;

            case BinaryOperatorType.Modulus:
                methodGroup = operators.RemainderOperators;
                break;

            case BinaryOperatorType.Addition:
                methodGroup = operators.AdditionOperators;
                {
                    if (lhsType.Kind == TypeKind.Enum)
                    {
                        // E operator +(E x, U y);
                        IType underlyingType = MakeNullable(rc, ResolveContext.GetEnumUnderlyingType(lhsType), isNullable);
                        if (rc.TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs))
                        {
                            return(HandleEnumOperator(rc, isNullable, lhsType, op, lhs, rhs));
                        }
                    }
                    if (rhsType.Kind == TypeKind.Enum)
                    {
                        // E operator +(U x, E y);
                        IType underlyingType = MakeNullable(rc, ResolveContext.GetEnumUnderlyingType(rhsType), isNullable);
                        if (rc.TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs))
                        {
                            return(HandleEnumOperator(rc, isNullable, rhsType, op, lhs, rhs));
                        }
                    }

                    if (lhsType.Kind == TypeKind.Delegate && rc.TryConvert(ref rhs, lhsType))
                    {
                        return(SetOperationInformations(rc, lhsType, lhs, op, rhs));
                    }
                    else if (rhsType.Kind == TypeKind.Delegate && rc.TryConvert(ref lhs, rhsType))
                    {
                        return(SetOperationInformations(rc, rhsType, lhs, op, rhs));
                    }

                    if (lhsType is PointerTypeSpec)
                    {
                        methodGroup = new[] {
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.Int32),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.UInt32),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.Int64),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.UInt64)
                        };
                    }
                    else if (rhsType is PointerTypeSpec)
                    {
                        methodGroup = new[] {
                            PointerArithmeticOperator(rc, rhsType, KnownTypeCode.Int32, rhsType),
                            PointerArithmeticOperator(rc, rhsType, KnownTypeCode.UInt32, rhsType),
                            PointerArithmeticOperator(rc, rhsType, KnownTypeCode.Int64, rhsType),
                            PointerArithmeticOperator(rc, rhsType, KnownTypeCode.UInt64, rhsType)
                        };
                    }
                    if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null)
                    {
                        return(new ErrorExpression(SpecialTypeSpec.NullType));
                    }
                }
                break;

            case BinaryOperatorType.Subtraction:
                methodGroup = operators.SubtractionOperators;
                {
                    if (lhsType.Kind == TypeKind.Enum)
                    {
                        // U operator –(E x, E y);
                        if (rc.TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs, allowConversionFromConstantZero: false))
                        {
                            return(HandleEnumSubtraction(rc, isNullable, lhsType, lhs, rhs));
                        }

                        // E operator –(E x, U y);
                        IType underlyingType = MakeNullable(rc, ResolveContext.GetEnumUnderlyingType(lhsType), isNullable);
                        if (rc.TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs))
                        {
                            return(HandleEnumOperator(rc, isNullable, lhsType, op, lhs, rhs));
                        }
                    }
                    if (rhsType.Kind == TypeKind.Enum)
                    {
                        // U operator –(E x, E y);
                        if (rc.TryConvertEnum(ref lhs, rhs.Type, ref isNullable, ref rhs, allowConversionFromConstantZero: false))
                        {
                            return(HandleEnumSubtraction(rc, isNullable, rhsType, lhs, rhs));
                        }

                        // E operator -(U x, E y);
                        IType underlyingType = MakeNullable(rc, ResolveContext.GetEnumUnderlyingType(rhsType), isNullable);
                        if (rc.TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs))
                        {
                            return(HandleEnumOperator(rc, isNullable, rhsType, op, lhs, rhs));
                        }
                    }

                    if (lhsType.Kind == TypeKind.Delegate && rc.TryConvert(ref rhs, lhsType))
                    {
                        return(SetOperationInformations(rc, lhsType, lhs, op, rhs));
                    }
                    else if (rhsType.Kind == TypeKind.Delegate && rc.TryConvert(ref lhs, rhsType))
                    {
                        return(SetOperationInformations(rc, rhsType, lhs, op, rhs));
                    }

                    if (lhsType is PointerTypeSpec)
                    {
                        if (rhsType is PointerTypeSpec)
                        {
                            IType int64 = rc.compilation.FindType(KnownTypeCode.Int64);
                            if (lhsType.Equals(rhsType))
                            {
                                return(SetOperationInformations(rc, int64, lhs, op, rhs));
                            }
                            else
                            {
                                return(new ErrorExpression(int64));
                            }
                        }
                        methodGroup = new[] {
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.Int32),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.UInt32),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.Int64),
                            PointerArithmeticOperator(rc, lhsType, lhsType, KnownTypeCode.UInt64)
                        };
                    }

                    if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null)
                    {
                        return(new ErrorExpression(SpecialTypeSpec.NullType));
                    }
                }
                break;

            case BinaryOperatorType.LeftShift:
                methodGroup = operators.ShiftLeftOperators;
                break;

            case BinaryOperatorType.RightShift:
                methodGroup = operators.ShiftRightOperators;
                break;

            case BinaryOperatorType.RotateRight:
                methodGroup = operators.RotateRightOperators;
                break;

            case BinaryOperatorType.RotateLeft:
                methodGroup = operators.RotateLeftOperators;
                break;


            case BinaryOperatorType.Equality:
            case BinaryOperatorType.Inequality:
            case BinaryOperatorType.LessThan:
            case BinaryOperatorType.GreaterThan:
            case BinaryOperatorType.LessThanOrEqual:
            case BinaryOperatorType.GreaterThanOrEqual:
            {
                if (lhsType.Kind == TypeKind.Enum && rc.TryConvert(ref rhs, lhs.Type))
                {
                    // bool operator op(E x, E y);
                    return(HandleEnumComparison(rc, op, lhsType, isNullable, lhs, rhs));
                }
                else if (rhsType.Kind == TypeKind.Enum && rc.TryConvert(ref lhs, rhs.Type))
                {
                    // bool operator op(E x, E y);
                    return(HandleEnumComparison(rc, op, rhsType, isNullable, lhs, rhs));
                }
                else if (lhsType is PointerTypeSpec && rhsType is PointerTypeSpec)
                {
                    return(SetOperationInformations(rc, rc.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(SetOperationInformations(rc, rc.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 (rc.TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs))
                    {
                        return(HandleEnumOperator(rc, isNullable, lhsType, op, lhs, rhs));
                    }
                }

                if (rhsType.Kind == TypeKind.Enum)
                {
                    // bool operator op(E x, E y);
                    if (rc.TryConvertEnum(ref lhs, rhs.Type, ref isNullable, ref rhs))
                    {
                        return(HandleEnumOperator(rc, 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.LogicalAnd:
                methodGroup = operators.LogicalAndOperators;
                break;

            case BinaryOperatorType.LogicalOr:
                methodGroup = operators.LogicalOrOperators;
                break;

            default:
                throw new InvalidOperationException();
            }
            OverloadResolution builtinOperatorOR = rc.CreateOverloadResolution(new[] { lhs, rhs });

            foreach (var candidate in methodGroup)
            {
                builtinOperatorOR.AddCandidate(candidate);
            }
            VSharpOperators.BinaryOperatorMethod m = (VSharpOperators.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(SetUserDefinedOperationInformations(rc, userDefinedOperatorOR));
                }
                else
                {
                    return(new ErrorExpression(resultType));
                }
            }
            else if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && m.CanEvaluateAtCompileTime)
            {
                object val;
                try
                {
                    val = m.Invoke(rc, lhs.ConstantValue, rhs.ConstantValue);
                }
                catch (ArithmeticException)
                {
                    return(new ErrorExpression(resultType));
                }
                return(Constant.CreateConstantFromValue(rc, resultType, val, loc));
            }
            else
            {
                lhs = rc.Convert(lhs, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]);
                rhs = rc.Convert(rhs, m.Parameters[1].Type, builtinOperatorOR.ArgumentConversions[1]);
                return(SetOperationInformations(rc, resultType, lhs, op, rhs,
                                                builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator));
            }
        }