private IType CheckBinaryLikeExpression()

in Public/Src/FrontEnd/TypeScript.Net/TypeScript.Net/TypeChecking/Checker.cs [15523:15813]


        private IType CheckBinaryLikeExpression(IExpression left, INode operatorToken, IExpression right, ITypeMapper contextualMapper = null, INode errorNode = null)
        {
            var @operator = operatorToken.Kind;

            if (@operator == SyntaxKind.EqualsToken &&
                (left.Kind == SyntaxKind.ObjectLiteralExpression || left.Kind == SyntaxKind.ArrayLiteralExpression))
            {
                return CheckDestructuringAssignment(
                        left,
                        CheckExpression(right, contextualMapper),
                        contextualMapper);
            }

            var leftType = CheckExpression(left, contextualMapper);
            var rightType = CheckExpression(right, contextualMapper);

            switch (@operator)
            {
                case SyntaxKind.AsteriskToken:
                case SyntaxKind.AsteriskAsteriskToken:
                case SyntaxKind.AsteriskEqualsToken:
                case SyntaxKind.AsteriskAsteriskEqualsToken:
                case SyntaxKind.SlashToken:
                case SyntaxKind.SlashEqualsToken:
                case SyntaxKind.PercentToken:
                case SyntaxKind.PercentEqualsToken:
                case SyntaxKind.MinusToken:
                case SyntaxKind.MinusEqualsToken:
                case SyntaxKind.LessThanLessThanToken:
                case SyntaxKind.LessThanLessThanEqualsToken:
                case SyntaxKind.GreaterThanGreaterThanToken:
                case SyntaxKind.GreaterThanGreaterThanEqualsToken:
                case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
                case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
                case SyntaxKind.BarToken:
                case SyntaxKind.BarEqualsToken:
                case SyntaxKind.CaretToken:
                case SyntaxKind.CaretEqualsToken:
                case SyntaxKind.AmpersandToken:
                case SyntaxKind.AmpersandEqualsToken:
                    // TypeScript 1.0 spec (April 2014): 4.19.1
                    // These operators require their operands to be of type Any, the Number primitive type,
                    // or an enum type. Operands of an enum type are treated
                    // as having the primitive type Number. If one operand is the null or undefined value,
                    // it is treated as having the type of the other operand.
                    // The result is always of the Number primitive type.
                    if ((leftType.Flags & (TypeFlags.Undefined | TypeFlags.Null)) != TypeFlags.None)
                    {
                        leftType = rightType;
                    }

                    if ((rightType.Flags & (TypeFlags.Undefined | TypeFlags.Null)) != TypeFlags.None)
                    {
                        rightType = leftType;
                    }

                    SyntaxKind? suggestedOperator = null;

                    // if a user tries to apply a bitwise operator to 2 bool operands
                    // try and return them a helpful suggestion
                    if ((leftType.Flags & TypeFlags.Boolean) != TypeFlags.None &&
                        (rightType.Flags & TypeFlags.Boolean) != TypeFlags.None &&
                        (suggestedOperator = GetSuggestedBooleanOperator(operatorToken.Kind)) != null)
                    {
                        Error(
                            errorNode ?? operatorToken,
                            Errors.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead,
                            TokenToString(operatorToken.Kind),
                            TokenToString(suggestedOperator.Value));
                    }
                    else
                    {
                        // otherwise just check each operand separately and report errors as normal
                        var leftOk = CheckArithmeticOperandType(
                                        left,
                                        leftType,
                                        Errors.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_or_an_enum_type);

                        var rightOk = CheckArithmeticOperandType(
                                        right,
                                        rightType,
                                        Errors.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_or_an_enum_type);

                        if (leftOk && rightOk)
                        {
                            CheckAssignmentOperator(m_numberType);
                        }
                    }

                    return m_numberType;

                case SyntaxKind.PlusToken:
                case SyntaxKind.PlusEqualsToken:
                    // TypeScript 1.0 spec (April 2014): 4.19.2
                    // The binary + operator requires both operands to be of the Number primitive type or an enum type,
                    // or at least one of the operands to be of type Any or the String primitive type.

                    // If one operand is the null or undefined value, it is treated as having the type of the other operand.
                    if ((leftType.Flags & (TypeFlags.Undefined | TypeFlags.Null)) != TypeFlags.None)
                    {
                        leftType = rightType;
                    }

                    if ((rightType.Flags & (TypeFlags.Undefined | TypeFlags.Null)) != TypeFlags.None)
                    {
                        rightType = leftType;
                    }

                    IType resultType = null;

                    if (IsTypeOfKind(leftType, TypeFlags.NumberLike) &&
                        IsTypeOfKind(rightType, TypeFlags.NumberLike))
                    {
                        // Operands of an enum type are treated as having the primitive type Number.
                        // If both operands are of the Number primitive type, the result is of the Number primitive type.
                        resultType = m_numberType;
                    }
                    else
                    {
                        if (IsTypeOfKind(leftType, TypeFlags.StringLike) ||
                            IsTypeOfKind(rightType, TypeFlags.StringLike))
                        {
                            // If one or both operands are of the String primitive type, the result is of the String primitive type.
                            resultType = m_stringType;
                        }
                        else if (IsTypeAny(leftType) || IsTypeAny(rightType))
                        {
                            // Otherwise, the result is of type Any.
                            // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we.
                            resultType = (leftType == m_unknownType) || (rightType == m_unknownType) ?
                                            m_unknownType :
                                            m_anyType;
                        }

                        // Symbols are not allowed at all in arithmetic expressions
                        if (resultType != null && !CheckForDisallowedEsSymbolOperand(@operator))
                        {
                            return resultType;
                        }
                    }

                    if (resultType == null)
                    {
                        ReportOperatorError();
                        return m_anyType;
                    }

                    if (@operator == SyntaxKind.PlusEqualsToken)
                    {
                        CheckAssignmentOperator(resultType);
                    }

                    return resultType;

                case SyntaxKind.LessThanToken:
                case SyntaxKind.GreaterThanToken:
                case SyntaxKind.LessThanEqualsToken:
                case SyntaxKind.GreaterThanEqualsToken:
                case SyntaxKind.EqualsEqualsToken:
                case SyntaxKind.ExclamationEqualsToken:
                case SyntaxKind.EqualsEqualsEqualsToken:
                case SyntaxKind.ExclamationEqualsEqualsToken:
                    if (@operator == SyntaxKind.LessThanToken ||
                        @operator == SyntaxKind.GreaterThanToken ||
                        @operator == SyntaxKind.LessThanEqualsToken ||
                        @operator == SyntaxKind.GreaterThanEqualsToken)
                    {
                        if (!CheckForDisallowedEsSymbolOperand(@operator))
                        {
                            return m_booleanType;
                        }
                    }

                    if (!IsTypeComparableTo(leftType, rightType) && !IsTypeComparableTo(rightType, leftType))
                    {
                        ReportOperatorError();
                    }

                    return m_booleanType;
                case SyntaxKind.InstanceOfKeyword:
                    return CheckInstanceOfExpression(
                            left,
                            right,
                            leftType,
                            rightType);

                case SyntaxKind.InKeyword:
                    return CheckInExpression(
                            left,
                            right,
                            leftType,
                            rightType);

                case SyntaxKind.AmpersandAmpersandToken:
                    return rightType;

                case SyntaxKind.BarBarToken:
                    return GetUnionType(new List<IType> { leftType, rightType });

                case SyntaxKind.EqualsToken:
                    CheckAssignmentOperator(rightType);
                    return GetRegularTypeOfObjectLiteral(rightType);

                case SyntaxKind.CommaToken:
                    return rightType;
            }

            // TODO: Debug.Assert?
            return null;

            // Return true if there was no error, false if there was an error.
            bool CheckForDisallowedEsSymbolOperand(SyntaxKind op)
            {
                var offendingSymbolOperand = MaybeTypeOfKind(leftType, TypeFlags.EsSymbol) ?
                                                left :
                                                    MaybeTypeOfKind(rightType, TypeFlags.EsSymbol) ?
                                                    right :
                                                    null;
                if (offendingSymbolOperand != null)
                {
                    Error(
                        offendingSymbolOperand,
                        Errors.The_0_operator_cannot_be_applied_to_type_symbol,
                        TokenToString(op));

                    return false;
                }

                return true;
            }

            SyntaxKind? GetSuggestedBooleanOperator(SyntaxKind op)
            {
                switch (op)
                {
                    case SyntaxKind.BarToken:
                    case SyntaxKind.BarEqualsToken:
                        return SyntaxKind.BarBarToken;

                    case SyntaxKind.CaretToken:
                    case SyntaxKind.CaretEqualsToken:
                        return SyntaxKind.ExclamationEqualsEqualsToken;

                    case SyntaxKind.AmpersandToken:
                    case SyntaxKind.AmpersandEqualsToken:
                        return SyntaxKind.AmpersandAmpersandToken;

                    default:
                        return null;
                }
            }

            void CheckAssignmentOperator(IType valueType)
            {
                if (m_produceDiagnostics &&
                    @operator >= SyntaxKind.FirstAssignment && @operator <= SyntaxKind.LastAssignment)
                {
                    // TypeScript 1.0 spec (April 2014): 4.17
                    // An assignment of the form
                    //    VarExpr = ValueExpr
                    // requires VarExpr to be classified as a reference
                    // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1)
                    // and the type of the non - compound operation to be assignable to the type of VarExpr.
                    var ok = CheckReferenceExpression(
                                left,
                                Errors.Invalid_left_hand_side_of_assignment_expression,
                                Errors.Left_hand_side_of_assignment_expression_cannot_be_a_constant);

                    // Use default messages
                    if (ok)
                    {
                        // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
                        CheckTypeAssignableTo(
                            valueType,
                            leftType,
                            left,
                            /*headMessage*/ null);
                    }
                }
            }

            void ReportOperatorError()
            {
                Error(
                    errorNode ?? operatorToken,
                    Errors.Operator_0_cannot_be_applied_to_types_1_and_2,
                    TokenToString(operatorToken.Kind),
                    TypeToString(leftType),
                    TypeToString(rightType));
            }
        }