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));
}
}