* Copyright (c) 2021-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "arithmetic.h"
#include "checker/ETSAnalyzerHelpers.h"
#include "checker/types/ets/etsObjectType.h"
#include "checker/types/ets/etsObjectTypeConstants.h"
#include "checker/types/ets/etsTupleType.h"
#include "checker/types/globalTypesHolder.h"
#include "checker/types/typeError.h"
#include "ir/ets/etsUnionType.h"
#include "lexer/token/token.h"
#include "compiler/lowering/util.h"
namespace ark::es2panda::checker {
struct BinaryArithmOperands {
ir::BinaryExpression *expr;
checker::Type *typeL;
checker::Type *typeR;
checker::Type *reducedL;
checker::Type *reducedR;
};
struct BinaryOperatorParams {
ir::Expression *left;
ir::Expression *right;
ir::Expression *expr;
lexer::TokenType operationType;
lexer::SourcePosition pos;
bool isEqualOp;
};
struct TypeParams {
checker::Type *leftType;
checker::Type *rightType;
Type *unboxedL;
Type *unboxedR;
};
struct RelationalContext {
lexer::TokenType op;
lexer::SourcePosition pos;
};
enum class BinaryExpressionValidity {
NO_ERR = 0U,
LHS_ERR = 1U << 0U,
RHS_ERR = 1U << 1U,
EITHER_ERR = 1U << 2U,
BOTH_ERR = LHS_ERR | RHS_ERR,
};
static BinaryArithmOperands GetBinaryOperands(ETSChecker *checker, ir::BinaryExpression *expr)
{
auto typeL = expr->Left()->Check(checker);
auto typeR = expr->Right()->Check(checker);
auto unboxedL = checker->MaybeUnboxType(typeL);
auto unboxedR = checker->MaybeUnboxType(typeR);
return {expr, typeL, typeR, unboxedL, unboxedR};
}
static void LogOperatorCannotBeApplied(ETSChecker *checker, lexer::TokenType op, Type *typeL, Type *typeR,
lexer::SourcePosition pos)
{
checker->LogError(diagnostic::BINOP_INVALID_TYPE, {TokenToString(op), typeL, typeR}, pos);
}
static void LogOperatorCannotBeApplied(ETSChecker *checker, BinaryArithmOperands const &ops)
{
LogOperatorCannotBeApplied(checker, ops.expr->OperatorType(), ops.typeL, ops.typeR, ops.expr->Start());
}
static inline void RepairTypeErrorsInOperands(ETSChecker *checker, Type **left, Type **right)
{
if (IsTypeError(*left)) {
*left = checker->GetNonConstantType(*right);
}
if (IsTypeError(*right)) {
*right = checker->GetNonConstantType(*left);
}
}
static inline BinaryArithmOperands RepairTypeErrorsInOperands(ETSChecker *checker, BinaryArithmOperands const &ops)
{
BinaryArithmOperands res = ops;
RepairTypeErrorsInOperands(checker, &res.typeL, &res.typeR);
RepairTypeErrorsInOperands(checker, &res.reducedL, &res.reducedR);
return res;
}
static inline void RepairTypeErrorWithDefault(Type **type, Type *dflt)
{
if (IsTypeError(*type)) {
*type = dflt;
}
}
static bool IsNegativeBigIntLiteralExpression(const ir::Expression *expr)
{
if (expr == nullptr || !expr->IsUnaryExpression()) {
return false;
}
auto const *unaryExpr = expr->AsUnaryExpression();
return unaryExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_MINUS && unaryExpr->Argument()->IsBigIntLiteral();
}
static bool CheckOpArgsTypeEq(ETSChecker *checker, Type *left, Type *right, Type *type)
{
return ((left != nullptr) && (right != nullptr) && checker->IsTypeIdenticalTo(left, type) &&
checker->IsTypeIdenticalTo(right, type));
}
static bool FindOpArgsType(ETSChecker *checker, Type *left, Type *right, Type *target)
{
return (checker->Relation()->IsSupertypeOf(target, left) || checker->Relation()->IsSupertypeOf(target, right));
}
bool ETSChecker::CheckIfNumeric(Type *type)
{
if (type == nullptr) {
return false;
}
if (type->IsETSPrimitiveType()) {
return type->HasTypeFlag(TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC) && !type->HasTypeFlag(TypeFlag::CHAR);
}
auto *unboxed = MaybeUnboxInRelation(type);
return (unboxed != nullptr) && unboxed->HasTypeFlag(TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC) &&
!unboxed->HasTypeFlag(TypeFlag::CHAR);
}
bool ETSChecker::CheckIfFloatingPoint(Type *type)
{
if (type == nullptr) {
return false;
}
auto *unboxed = MaybeUnboxInRelation(type);
return (unboxed != nullptr) && (unboxed->IsFloatType() || unboxed->IsDoubleType());
}
static Type *EffectivePrimitiveTypeOfNumericOp(ETSChecker *checker, Type *left, Type *right)
{
if (left->IsDoubleType() || right->IsDoubleType()) {
return checker->GlobalDoubleType();
}
if (left->IsFloatType() || right->IsFloatType()) {
return checker->GlobalFloatType();
}
if (left->IsLongType() || right->IsLongType()) {
return checker->GlobalLongType();
}
if (left->IsCharType() && right->IsCharType()) {
return checker->GlobalCharType();
}
return checker->GlobalIntType();
}
static Type *TryConvertToComparablePrimitiveType(ETSChecker *checker, Type *type)
{
if (type == nullptr) {
return nullptr;
}
if (type->IsETSUndefinedType()) {
return nullptr;
}
if (type->IsETSNumericEnumType()) {
if (type->AsETSObjectType()->SuperType() != nullptr &&
!type->AsETSObjectType()->SuperType()->TypeArguments().empty()) {
auto *baseEnumArg = type->AsETSObjectType()->SuperType()->TypeArguments()[0];
return checker->MaybeUnboxInRelation(baseEnumArg);
}
return checker->GlobalIntType();
}
if (type->IsETSStringEnumType()) {
return checker->GlobalETSStringLiteralType();
}
return checker->MaybeUnboxInRelation(type);
}
static Type *EffectiveTypeOfNumericOp(ETSChecker *checker, Type *left, Type *right)
{
ES2PANDA_ASSERT(checker->CheckIfNumeric(left) && checker->CheckIfNumeric(right));
auto bothBoxed = left->IsETSUnboxableObject() && right->IsETSUnboxableObject();
if (!bothBoxed) {
return EffectivePrimitiveTypeOfNumericOp(checker, left, right);
}
auto globalTypesHolder = checker->GetGlobalTypesHolder();
if (FindOpArgsType(checker, left, right, globalTypesHolder->GlobalDoubleBuiltinType())) {
return globalTypesHolder->GlobalDoubleBuiltinType();
}
if (FindOpArgsType(checker, left, right, globalTypesHolder->GlobalFloatBuiltinType())) {
return globalTypesHolder->GlobalFloatBuiltinType();
}
if (FindOpArgsType(checker, left, right, globalTypesHolder->GlobalLongBuiltinType())) {
return globalTypesHolder->GlobalLongBuiltinType();
}
return globalTypesHolder->GlobalIntegerBuiltinType();
}
static Type *BinaryGetPromotedType(ETSChecker *checker, Type *left, Type *right, bool const promote)
{
Type *const unboxedL = TryConvertToComparablePrimitiveType(checker, left);
Type *const unboxedR = TryConvertToComparablePrimitiveType(checker, right);
if (unboxedL == nullptr || unboxedR == nullptr) {
return nullptr;
}
Type *typeL = left;
Type *typeR = right;
bool const bothBoxed = !typeL->IsETSPrimitiveType() && !typeR->IsETSPrimitiveType();
if (!promote) {
return typeR;
}
if (!bothBoxed) {
if (unboxedL->IsETSEnumType() || unboxedR->IsETSEnumType()) {
return nullptr;
}
if (!typeL->IsETSPrimitiveType()) {
typeL = checker->MaybeUnboxType(typeL);
}
if (!typeR->IsETSPrimitiveType()) {
typeR = checker->MaybeUnboxType(typeR);
}
}
if (checker->CheckIfNumeric(typeL) && checker->CheckIfNumeric(typeR)) {
return EffectiveTypeOfNumericOp(checker, typeL, typeR);
}
if (checker->CheckIfNumeric(typeR)) {
return typeR;
}
return typeL;
}
bool ETSChecker::CheckBinaryOperatorForBigInt(Type *left, Type *right, lexer::TokenType op)
{
if ((left == nullptr) || (right == nullptr)) {
return false;
}
bool leftIsBigInt = left->IsETSBigIntType();
bool rightIsBigInt = right->IsETSBigIntType();
if ((leftIsBigInt && CheckIfNumeric(right)) || (rightIsBigInt && CheckIfNumeric(left))) {
switch (op) {
case lexer::TokenType::PUNCTUATOR_GREATER_THAN:
case lexer::TokenType::PUNCTUATOR_LESS_THAN:
case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL:
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL:
return true;
default:
break;
}
}
if (!leftIsBigInt || !rightIsBigInt) {
return false;
}
switch (op) {
case lexer::TokenType::KEYW_INSTANCEOF:
case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT:
return false;
default:
break;
}
return true;
}
bool ETSChecker::CheckBinaryPlusMultDivOperandsForUnionType(const Type *leftType, const Type *rightType,
const ir::Expression *left, const ir::Expression *right)
{
std::stringstream ss;
if (leftType->IsETSUnionType()) {
LogError(diagnostic::BINOP_ON_UNION, {leftType}, left->Start());
return false;
}
if (rightType->IsETSUnionType()) {
LogError(diagnostic::BINOP_ON_UNION, {rightType}, right->Start());
return false;
}
return true;
}
void ETSChecker::SetGenerateValueOfFlags(std::tuple<checker::Type *, checker::Type *, Type *, Type *> types,
std::tuple<ir::Expression *, ir::Expression *> nodes)
{
auto [leftType, rightType, _, __] = types;
auto [left, right] = nodes;
if (leftType->IsETSEnumType()) {
left->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
if (rightType->IsETSEnumType()) {
right->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
}
static bool TypeIsAppropriateForArithmetic(const checker::Type *type, ETSChecker *checker)
{
return type->HasTypeFlag(TypeFlag::ETS_CONVERTIBLE_TO_NUMERIC) ||
(type->IsETSObjectType() &&
checker->Relation()->IsSupertypeOf(checker->GetGlobalTypesHolder()->GlobalNumericBuiltinType(), type));
}
static checker::Type *CheckBinaryOperatorForNumericEnums(ETSChecker *checker, checker::Type *const leftType,
checker::Type *const rightType)
{
if (!leftType->IsETSEnumType() && !rightType->IsETSEnumType()) {
return nullptr;
}
if (TypeIsAppropriateForArithmetic(leftType, checker) && TypeIsAppropriateForArithmetic(rightType, checker)) {
Type *leftNumeric;
if (leftType->IsETSNumericEnumType()) {
leftNumeric = checker->MaybeBoxInRelation(TryConvertToComparablePrimitiveType(checker, leftType));
} else {
leftNumeric = leftType;
}
Type *rightNumeric;
if (rightType->IsETSNumericEnumType()) {
rightNumeric = checker->MaybeBoxInRelation(TryConvertToComparablePrimitiveType(checker, rightType));
} else {
rightNumeric = rightType;
}
return EffectiveTypeOfNumericOp(checker, leftNumeric, rightNumeric);
}
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorMulDivMod(
std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op, bool isEqualOp,
std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
auto const promotedType = BinaryGetPromotedType(this, leftType, rightType, !isEqualOp);
if (!CheckBinaryPlusMultDivOperandsForUnionType(leftType, rightType, left, right)) {
return GlobalTypeError();
}
if (promotedType == nullptr || !CheckIfNumeric(leftType) || !CheckIfNumeric(rightType)) {
auto type = CheckBinaryOperatorForNumericEnums(this, leftType, rightType);
if (type != nullptr) {
return type;
}
LogError(diagnostic::OP_NONNUMERIC, {}, pos);
return GlobalTypeError();
}
return promotedType;
}
checker::Type *ETSChecker::CheckBinaryBitwiseOperatorForNumericEnums(checker::Type *const leftType,
checker::Type *const rightType)
{
if (!leftType->IsETSEnumType() && !rightType->IsETSEnumType()) {
return nullptr;
}
auto checkForEnumType = [=](auto type, bool hasLong) -> bool {
ETSObjectFlags floatingPointType = hasLong ? ETSObjectFlags::BUILTIN_DOUBLE : ETSObjectFlags::BUILTIN_FLOAT;
ETSObjectFlags integralType = hasLong ? ETSObjectFlags::BUILTIN_LONG : ETSObjectFlags::BUILTIN_INT;
return (type->AsETSNumericEnumType()->CheckBuiltInType(this, floatingPointType) ||
type->AsETSNumericEnumType()->CheckBuiltInType(this, integralType));
};
if (TypeIsAppropriateForArithmetic(leftType, this) && TypeIsAppropriateForArithmetic(rightType, this)) {
if (leftType->IsETSNumericEnumType() && rightType->IsETSNumericEnumType()) {
if (checkForEnumType(leftType, false) || checkForEnumType(rightType, false)) {
return GlobalIntBuiltinType();
}
if (checkForEnumType(leftType, true) || checkForEnumType(rightType, true)) {
return GlobalLongBuiltinType();
}
}
if (leftType->IsFloatType() || rightType->IsFloatType()) {
return GlobalIntBuiltinType();
}
if (leftType->IsDoubleType() || rightType->IsDoubleType()) {
return GlobalLongBuiltinType();
}
if (leftType->IsLongType() || rightType->IsLongType()) {
return GlobalLongBuiltinType();
}
return GlobalIntBuiltinType();
}
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorExponentiation(
std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op, bool isEqualOp,
std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
if (IsNegativeBigIntLiteralExpression(right)) {
LogError(diagnostic::EXPONENTIATION_BIGINT_NEGATIVE_EXPONENT, {}, right->Start());
return GlobalTypeError();
}
auto const promotedType = BinaryGetPromotedType(this, leftType, rightType, !isEqualOp);
if (promotedType == nullptr || !CheckIfNumeric(leftType) || !CheckIfNumeric(rightType)) {
if (operationType == lexer::TokenType::PUNCTUATOR_EXPONENTIATION) {
LogError(diagnostic::EXPONENTIATION_TYPE_LIMITATION, {}, pos);
}
return GlobalTypeError();
}
return GlobalDoubleType();
}
static checker::Type *CheckBinaryOperatorPlusForEnums(ETSChecker *checker, checker::Type *const leftType,
checker::Type *const rightType)
{
if (auto numericType = CheckBinaryOperatorForNumericEnums(checker, leftType, rightType); numericType != nullptr) {
return numericType;
}
if ((leftType->IsETSStringEnumType() && (rightType->IsETSStringType() || rightType->IsETSStringEnumType())) ||
(rightType->IsETSStringEnumType() && (leftType->IsETSStringType() || leftType->IsETSStringEnumType()))) {
return checker->GlobalETSStringLiteralType();
}
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorPlus(
std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op, bool isEqualOp,
std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
if (leftType->IsETSStringType() || rightType->IsETSStringType()) {
if (operationType == lexer::TokenType::PUNCTUATOR_MINUS ||
operationType == lexer::TokenType::PUNCTUATOR_MINUS_EQUAL) {
LogError(diagnostic::OP_NONNUMERIC, {}, pos);
return GlobalTypeError();
}
return HandleStringConcatenation(leftType, rightType);
}
if (!CheckBinaryPlusMultDivOperandsForUnionType(leftType, rightType, left, right)) {
return GlobalTypeError();
}
auto const promotedType = BinaryGetPromotedType(this, leftType, rightType, !isEqualOp);
if (promotedType == nullptr || !CheckIfNumeric(rightType) || !CheckIfNumeric(leftType)) {
auto type = CheckBinaryOperatorPlusForEnums(this, leftType, rightType);
if (type != nullptr) {
return type;
}
LogError(diagnostic::BINOP_NONARITHMETIC_TYPE, {}, pos);
return GlobalTypeError();
}
return promotedType;
}
[[maybe_unused]] static checker::Type *GetBitwiseCompatibleType(ETSChecker *checker, Type *const type)
{
switch (checker->ETSType(type)) {
case TypeFlag::BYTE: {
return checker->GlobalByteType();
}
case TypeFlag::SHORT: {
return checker->GlobalShortType();
}
case TypeFlag::CHAR: {
return checker->GlobalCharType();
}
case TypeFlag::INT:
case TypeFlag::FLOAT: {
return checker->GlobalIntType();
}
case TypeFlag::LONG:
case TypeFlag::DOUBLE: {
return checker->GlobalLongType();
}
default: {
ES2PANDA_UNREACHABLE();
}
}
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorShift(
std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op, bool isEqualOp,
std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
RepairTypeErrorWithDefault(&leftType, GlobalIntBuiltinType());
RepairTypeErrorWithDefault(&rightType, GlobalIntBuiltinType());
RepairTypeErrorWithDefault(&unboxedL, GlobalIntType());
RepairTypeErrorWithDefault(&unboxedR, GlobalIntType());
if (leftType->IsETSUnionType() || rightType->IsETSUnionType()) {
LogError(diagnostic::BINOP_UNION, {}, pos);
return GlobalTypeError();
}
auto promotedLeftType = GetUnaryOperatorPromotedType(leftType, !isEqualOp);
auto promotedRightType = GetUnaryOperatorPromotedType(rightType, !isEqualOp);
if (promotedLeftType == nullptr || promotedRightType == nullptr || !CheckIfNumeric(promotedLeftType) ||
!CheckIfNumeric(promotedRightType)) {
auto type = CheckBinaryBitwiseOperatorForNumericEnums(leftType, rightType);
if (type != nullptr) {
return type;
}
LogError(diagnostic::OP_NONNUMERIC, {}, pos);
return GlobalTypeError();
}
auto isPrim = promotedLeftType->IsETSPrimitiveType();
auto unboxedProm = MaybeUnboxType(promotedLeftType);
if (unboxedProm->IsFloatType() || unboxedProm->IsIntType()) {
return isPrim ? GlobalIntType() : GetGlobalTypesHolder()->GlobalIntegerBuiltinType();
}
if (unboxedProm->IsLongType() || unboxedProm->IsDoubleType()) {
return isPrim ? GlobalLongType() : GetGlobalTypesHolder()->GlobalLongBuiltinType();
}
if (unboxedProm->IsByteType() || unboxedProm->IsShortType()) {
return promotedLeftType;
}
ES2PANDA_UNREACHABLE();
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorBitwise(
std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op, bool isEqualOp,
std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
if (leftType->IsETSUnionType() || rightType->IsETSUnionType()) {
LogError(diagnostic::BINOP_UNION, {}, pos);
return GlobalTypeError();
}
if (CheckOpArgsTypeEq(this, unboxedL, unboxedR, GlobalETSBooleanType())) {
return GetGlobalTypesHolder()->GlobalETSBooleanBuiltinType();
}
auto const promotedType = BinaryGetPromotedType(this, leftType, rightType, !isEqualOp);
if (promotedType == nullptr || !CheckIfNumeric(rightType) || !CheckIfNumeric(leftType)) {
auto type = CheckBinaryBitwiseOperatorForNumericEnums(leftType, rightType);
if (type != nullptr) {
return type;
}
LogError(diagnostic::OP_NONNUMERIC, {}, pos);
return GlobalTypeError();
}
SetGenerateValueOfFlags(types, {left, right});
auto isPrim = promotedType->IsETSPrimitiveType();
auto unboxedProm = MaybeUnboxType(promotedType);
if (unboxedProm->IsFloatType() || unboxedProm->IsIntType()) {
return isPrim ? GlobalIntType() : GetGlobalTypesHolder()->GlobalIntegerBuiltinType();
}
if (unboxedProm->IsLongType() || unboxedProm->IsDoubleType()) {
return isPrim ? GlobalLongType() : GetGlobalTypesHolder()->GlobalLongBuiltinType();
}
if (unboxedProm->IsByteType() || unboxedProm->IsShortType()) {
return promotedType;
}
return nullptr;
}
checker::Type *ETSChecker::CheckBinaryOperatorLogical(ir::Expression *left, ir::Expression *right,
checker::Type *leftType, checker::Type *rightType, Type *unboxedL,
Type *unboxedR)
{
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
if (leftType->IsETSEnumType() || rightType->IsETSEnumType()) {
left->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
right->RemoveAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
return CreateETSUnionType({MaybeBoxExpression(left), MaybeBoxExpression(right)});
}
if (right->IsNumberLiteral() && !left->IsNumberLiteral() && leftType->IsBuiltinNumeric()) {
return leftType;
}
if (left->IsNumberLiteral() && !right->IsNumberLiteral() && rightType->IsBuiltinNumeric()) {
return rightType;
}
if (IsTypeIdenticalTo(leftType, rightType)) {
return GetNonConstantType(leftType);
}
return CreateETSUnionType({MaybeBoxExpression(left), MaybeBoxExpression(right)});
}
static bool ContainsNumbers(ETSChecker *checker, Type *tp)
{
auto isSubtypeOfNumeric = [checker](Type *tp2) {
return checker->Relation()->IsSupertypeOf(checker->GetGlobalTypesHolder()->GlobalNumericBuiltinType(), tp2);
};
if (isSubtypeOfNumeric(tp)) {
return true;
}
if (tp->IsETSUnionType()) {
for (auto *constituent : tp->AsETSUnionType()->ConstituentTypes()) {
if (isSubtypeOfNumeric(constituent)) {
return true;
}
}
}
return false;
}
static bool HasBigIntNonBigIntEqualityPairing(ETSChecker *checker, Type *leftType, Type *rightType)
{
auto *ul = checker->MaybeUnboxType(checker->GetApparentType(leftType));
auto *ur = checker->MaybeUnboxType(checker->GetApparentType(rightType));
if (ul == nullptr || ur == nullptr) {
return false;
}
return ul->IsETSBigIntType() != ur->IsETSBigIntType();
}
bool ETSChecker::CheckValidEqualReferenceType(checker::Type *const leftType, checker::Type *const rightType)
{
auto isRelaxedType {[&](checker::Type *const type) -> bool {
return (type->IsETSObjectType() && type->AsETSObjectType()->IsGlobalETSObjectType()) || type->IsETSAnyType() ||
type->IsETSNullType() || type->IsETSUndefinedType();
}};
if (isRelaxedType(leftType) || isRelaxedType(rightType)) {
return true;
}
if (HasBigIntNonBigIntEqualityPairing(this, leftType, rightType)) {
return true;
}
if (ContainsNumbers(this, leftType) && ContainsNumbers(this, rightType)) {
return true;
}
if ((FindOpArgsType(this, leftType, rightType, GetGlobalTypesHolder()->GlobalNumericBuiltinType()) ||
FindOpArgsType(this, leftType, rightType, GetGlobalTypesHolder()->GlobalCharBuiltinType())) &&
FindOpArgsType(this, leftType, rightType, GetGlobalTypesHolder()->GlobalETSBooleanBuiltinType())) {
return false;
}
if (leftType->IsETSUnionType()) {
return leftType->AsETSUnionType()->IsOverlapWith(Relation(), rightType);
}
if (rightType->IsETSUnionType()) {
return rightType->AsETSUnionType()->IsOverlapWith(Relation(), leftType);
}
if (leftType->HasTypeFlag(TypeFlag::GENERIC) || rightType->HasTypeFlag(TypeFlag::GENERIC)) {
return true;
}
if (leftType->IsETSStringType() || rightType->IsETSStringType() || leftType->IsETSBigIntType() ||
rightType->IsETSBigIntType()) {
auto *const nonConstLhs = GetNonConstantType(leftType);
auto *const nonConstRhs = GetNonConstantType(rightType);
if (!Relation()->IsIdenticalTo(nonConstLhs, nonConstRhs) &&
!Relation()->IsIdenticalTo(nonConstRhs, nonConstLhs)) {
return false;
}
}
if (FindOpArgsType(this, leftType, rightType, GetGlobalTypesHolder()->GlobalNumericBuiltinType()) &&
(leftType->IsETSEnumType() || rightType->IsETSEnumType())) {
return true;
}
return leftType->IsETSEnumType() == rightType->IsETSEnumType();
}
std::tuple<Type *, Type *> ETSChecker::CheckBinaryOperatorStrictEqual(ir::Expression *left,
lexer::TokenType operationType,
lexer::SourcePosition pos,
checker::Type *leftType, checker::Type *rightType)
{
RepairTypeErrorsInOperands(this, &leftType, &rightType);
ERROR_TYPE_CHECK(this, leftType, return std::make_tuple(GlobalETSBooleanBuiltinType(), GlobalETSObjectType()));
if (!IsReferenceType(leftType) || !IsReferenceType(rightType)) {
LogError(diagnostic::BINOP_NOT_REFERENCE, {}, pos);
return {GlobalETSBooleanType(), GlobalETSObjectType()};
}
Relation()->SetNode(left);
if (!CheckValidEqualReferenceType(leftType, rightType)) {
LogOperatorCannotBeApplied(this, operationType, leftType, rightType, pos);
} else if (!Relation()->IsCastableTo(leftType, rightType) && !Relation()->IsCastableTo(rightType, leftType)) {
LogOperatorCannotBeApplied(this, operationType, leftType, rightType, pos);
}
return {GlobalETSBooleanType(), GlobalETSObjectType()};
}
static Type *HandelReferenceBinaryEquality(ETSChecker *checker, BinaryArithmOperands const &ops)
{
[[maybe_unused]] auto const [expr, typeL, typeR, reducedL, reducedR] = ops;
if ((typeR->IsETSNullType() && typeL->IsETSPrimitiveType()) ||
(typeL->IsETSNullType() && typeR->IsETSPrimitiveType())) {
return checker->CreateETSUnionType({typeL, typeR});
}
if (typeL->IsETSReferenceType() && typeR->IsETSReferenceType()) {
checker->Relation()->SetNode(expr->Left());
if (!checker->CheckValidEqualReferenceType(typeL, typeR)) {
LogOperatorCannotBeApplied(checker, ops);
return typeL;
}
return checker->CreateETSUnionType({typeL, typeR});
}
if ((reducedL->IsETSReferenceType() || reducedR->IsETSReferenceType()) &&
!(typeL->IsETSNullType() || typeL->IsETSUndefinedType()) &&
!(typeR->IsETSNullType() || typeR->IsETSUndefinedType())) {
auto *const boxedL = checker->MaybeBoxType(typeL);
auto *const boxedR = checker->MaybeBoxType(typeR);
checker->Relation()->SetNode(expr->Left());
if (!checker->CheckValidEqualReferenceType(boxedL, boxedR) ||
(!checker->Relation()->IsCastableTo(boxedL, boxedR) &&
!checker->Relation()->IsCastableTo(boxedR, boxedL))) {
LogOperatorCannotBeApplied(checker, ops);
return typeL;
}
return checker->CreateETSUnionType(
{checker->MaybeBoxExpression(expr->Left()), checker->MaybeBoxExpression(expr->Right())});
}
return nullptr;
}
static bool IsStaticallyDistinctReference(const ir::Expression *left, const ir::Expression *right)
{
if (left->IsETSNewClassInstanceExpression() && right->IsETSNewClassInstanceExpression()) {
return true;
}
auto isScriptFunctionReference = [](const ir::Expression *expr) {
if (!expr->IsIdentifier()) {
return false;
}
auto *var = expr->Variable();
if (var == nullptr || var->Declaration() == nullptr) {
return false;
}
return var->Declaration()->IsFunctionDecl();
};
if (isScriptFunctionReference(left) && isScriptFunctionReference(right) && left->Variable() != right->Variable()) {
return true;
}
return false;
}
static std::optional<bool> ShouldReturnPredefinedEqualityExpressionResult(ETSChecker *checker,
const ir::Expression *left,
const ir::Expression *right,
checker::Type *leftType,
checker::Type *rightType)
{
if (IsStaticallyDistinctReference(left, right)) {
return false;
}
if ((leftType->DefinitelyETSNullish() && !rightType->PossiblyETSNullish()) ||
(rightType->DefinitelyETSNullish() && !leftType->PossiblyETSNullish())) {
return false;
}
if (leftType->DefinitelyETSNullish() && rightType->DefinitelyETSNullish()) {
return true;
}
if (leftType->IsETSObjectType() && rightType->IsETSObjectType() &&
!checker->Relation()->IsCastableTo(leftType, rightType) &&
!checker->Relation()->IsCastableTo(rightType, leftType)) {
return false;
}
return std::nullopt;
}
static std::optional<bool> EvaluateEqualityExpressionResult(ETSChecker *checker, ir::Expression *left,
ir::Expression *right, checker::Type *leftType,
checker::Type *rightType)
{
if (IsTypeError(leftType) || IsTypeError(rightType)) {
return std::nullopt;
}
if (HasBigIntNonBigIntEqualityPairing(checker, leftType, rightType)) {
return false;
}
leftType = checker->MaybeUnboxType(checker->GetApparentType(leftType));
rightType = checker->MaybeUnboxType(checker->GetApparentType(rightType));
if (leftType->IsETSEnumType() && rightType->IsETSEnumType() &&
!checker->Relation()->IsIdenticalTo(leftType, rightType)) {
return std::nullopt;
}
if (leftType->IsConstantType() && rightType->IsConstantType()) {
return checker->Relation()->IsIdenticalTo(leftType, rightType);
}
return ShouldReturnPredefinedEqualityExpressionResult(checker, left, right, leftType, rightType);
}
static void MaybeLogCompileTimeEqualityWarning(ETSChecker *checker, const BinaryArithmOperands &ops,
lexer::TokenType operationType, const lexer::SourcePosition &pos)
{
ir::Expression *left = ops.expr->Left();
ir::Expression *right = ops.expr->Right();
auto result = EvaluateEqualityExpressionResult(checker, left, right, ops.typeL, ops.typeR);
if (!result.has_value()) {
return;
}
auto value = *result;
if (operationType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL ||
operationType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL) {
value = !value;
}
bool isOptionalChaining =
left->IsIdentifier() && left->AsIdentifier()->Name().StartsWith(compiler::GENSYM_CORE) &&
util::Helpers::FindAncestorGivenByType(ops.expr, ir::AstNodeType::CONDITIONAL_EXPRESSION) != nullptr;
if (isOptionalChaining) {
auto blockExpression = util::Helpers::FindAncestorGivenByType(ops.expr, ir::AstNodeType::BLOCK_EXPRESSION);
if (blockExpression == nullptr || blockExpression->OriginalNode() == nullptr ||
!blockExpression->OriginalNode()->IsChainExpression()) {
isOptionalChaining = false;
}
}
if (isOptionalChaining) {
checker->LogDiagnostic(value ? diagnostic::NULLISH_OPERAND : diagnostic::NON_NULLISH_OPERAND,
{"optional-chaining"}, pos);
} else {
checker->LogDiagnostic(
value ? diagnostic::EQUALITY_EXPRESSION_ALWAYS_TRUE : diagnostic::EQUALITY_EXPRESSION_ALWAYS_FALSE, pos);
}
}
static Type *CheckBinaryOperatorEqual(ETSChecker *checker, BinaryArithmOperands const &ops)
{
[[maybe_unused]] auto const [expr, typeL, typeR, reducedL, reducedR] = ops;
ERROR_TYPE_CHECK(checker, typeL, return checker->GlobalTypeError());
auto *const appL = checker->GetApparentType(typeL);
auto *const appR = checker->GetApparentType(typeR);
auto isPrimitiveLikeRef = [](Type *tp) {
return tp->IsETSObjectType() && tp->AsETSObjectType()->IsBoxedPrimitive();
};
if ((appL->IsETSFunctionType() && (!appR->IsETSReferenceType() || isPrimitiveLikeRef(appR))) ||
(appR->IsETSFunctionType() && (!appL->IsETSReferenceType() || isPrimitiveLikeRef(appL)))) {
LogOperatorCannotBeApplied(checker, ops);
return typeL;
}
if (reducedL->IsETSBooleanType() && reducedR->IsETSBooleanType()) {
if (reducedL->IsConstantType() && reducedR->IsConstantType()) {
return checker->GetGlobalTypesHolder()->GlobalETSBooleanBuiltinType();
}
if (checker->CheckIfNumeric(typeL) && checker->CheckIfNumeric(typeR) && typeL->IsETSUnboxableObject() &&
typeR->IsETSUnboxableObject()) {
return typeL;
}
return reducedL;
}
return HandelReferenceBinaryEquality(checker, ops);
}
static bool NonNumericTypesAreAppropriateForComparison(ETSChecker *checker, Type *leftType, Type *rightType)
{
leftType = checker->MaybeUnboxType(leftType);
rightType = checker->MaybeUnboxType(rightType);
if (rightType->IsETSStringType() && leftType->IsETSStringType()) {
return true;
}
if (leftType->IsETSEnumType() && rightType->IsETSEnumType()) {
return checker->Relation()->IsIdenticalTo(leftType, rightType);
}
if ((leftType->IsETSStringEnumType() && rightType->IsETSStringType()) ||
(leftType->IsETSStringType() && rightType->IsETSStringEnumType())) {
return true;
}
if ((leftType->IsETSPrimitiveType() && rightType->IsETSNumericEnumType()) ||
(leftType->IsETSNumericEnumType() && rightType->IsETSPrimitiveType())) {
return true;
}
return false;
}
static bool IsEqualityOperator(lexer::TokenType operationType)
{
return operationType == lexer::TokenType::PUNCTUATOR_EQUAL ||
operationType == lexer::TokenType::PUNCTUATOR_NOT_EQUAL ||
operationType == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL ||
operationType == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL;
}
static bool HasMismatchedBooleanComparands(Type *promotedType, Type *unboxedL, Type *unboxedR)
{
return promotedType != nullptr && unboxedL != nullptr && unboxedR != nullptr &&
unboxedL->IsETSBooleanType() != unboxedR->IsETSBooleanType();
}
static bool CheckUnionRelationalError(ETSChecker *checker, Type *leftType, Type *rightType,
const RelationalContext &ctx)
{
if ((leftType->IsETSUnionType() || rightType->IsETSUnionType()) && !IsEqualityOperator(ctx.op)) {
checker->LogError(diagnostic::BINOP_UNION, {}, ctx.pos);
return true;
}
return false;
}
static bool CheckStringEnumBooleanConflict(ETSChecker *checker, Type *leftType, Type *rightType,
const RelationalContext &ctx)
{
auto *ubL = checker->MaybeUnboxType(leftType);
auto *ubR = checker->MaybeUnboxType(rightType);
if ((leftType->IsETSStringEnumType() || rightType->IsETSStringEnumType()) &&
((ubL && ubL->IsETSBooleanType()) || (ubR && ubR->IsETSBooleanType()))) {
LogOperatorCannotBeApplied(checker, ctx.op, ubL ? ubL : leftType, ubR ? ubR : rightType, ctx.pos);
return true;
}
return false;
}
static bool CharRelationalOperandIsValid(ETSChecker *checker, Type *leftType, Type *rightType)
{
auto const leftUnboxed = checker->MaybeUnboxType(leftType);
auto const rightUnboxed = checker->MaybeUnboxType(rightType);
if (leftUnboxed == nullptr || rightUnboxed == nullptr) {
return true;
}
auto const leftIsChar = leftUnboxed->IsCharType();
auto const rightIsChar = rightUnboxed->IsCharType();
if (!leftIsChar && !rightIsChar) {
return true;
}
if (leftIsChar && rightIsChar) {
return true;
}
auto const &otherUnboxed = leftIsChar ? rightUnboxed : leftUnboxed;
auto const &otherOriginal = leftIsChar ? rightType : leftType;
return checker->CheckIfNumeric(otherUnboxed) && !otherOriginal->IsETSEnumType() &&
!otherOriginal->IsETSStringEnumType();
}
static bool CheckCharRelationalError(ETSChecker *checker, Type *leftType, Type *rightType, const RelationalContext &ctx)
{
if (!CharRelationalOperandIsValid(checker, leftType, rightType)) {
LogOperatorCannotBeApplied(checker, ctx.op, checker->GetNonConstantType(leftType),
checker->GetNonConstantType(rightType), ctx.pos);
return true;
}
return false;
}
std::tuple<Type *, Type *> ETSChecker::CheckBinaryOperatorLessGreater(ir::Expression *left, ir::Expression *right,
lexer::TokenType operationType,
lexer::SourcePosition pos, bool isEqualOp,
checker::Type *leftType, checker::Type *rightType,
Type *unboxedL, Type *unboxedR)
{
RepairTypeErrorsInOperands(this, &leftType, &rightType);
RepairTypeErrorsInOperands(this, &unboxedL, &unboxedR);
ERROR_TYPE_CHECK(this, leftType, return std::make_tuple(GlobalETSBooleanBuiltinType(), GlobalTypeError()));
if (CheckUnionRelationalError(this, leftType, rightType, {operationType, pos})) {
return {GlobalETSBooleanBuiltinType(), leftType};
}
auto const promotedType = BinaryGetPromotedType(this, leftType, rightType, !isEqualOp);
if (CheckStringEnumBooleanConflict(this, leftType, rightType, {operationType, pos})) {
return {GlobalETSBooleanBuiltinType(), leftType};
}
if (leftType->IsETSUnionType() || rightType->IsETSUnionType()) {
return {GlobalETSBooleanBuiltinType(),
CreateETSUnionType({MaybeBoxExpression(left), MaybeBoxExpression(right)})};
}
if (HasMismatchedBooleanComparands(promotedType, unboxedL, unboxedR)) {
if (HasBigIntNonBigIntEqualityPairing(this, leftType, rightType)) {
return {GlobalETSBooleanBuiltinType(), GlobalETSBooleanBuiltinType()};
}
LogOperatorCannotBeApplied(this, operationType, leftType, rightType, pos);
}
if (CheckCharRelationalError(this, leftType, rightType, {operationType, pos})) {
return {GlobalETSBooleanBuiltinType(), leftType};
}
if (promotedType == nullptr) {
if (!NonNumericTypesAreAppropriateForComparison(this, leftType, rightType)) {
LogError(diagnostic::BINOP_INCOMPARABLE, {}, pos);
}
return {GlobalETSBooleanBuiltinType(), GlobalETSBooleanBuiltinType()};
}
return {GlobalETSBooleanBuiltinType(), promotedType};
}
static bool IsTypeRetainedAfterErasure(Type const *const typeToCheck, bool const checkTypeParameter = true) noexcept
{
if (checkTypeParameter && typeToCheck->IsETSTypeParameter()) {
return false;
}
if (typeToCheck->IsETSFunctionType()) {
auto *signature = typeToCheck->AsETSFunctionType()->ArrowSignature();
if (!signature->ReturnType()->IsETSNeverType()) {
return false;
}
for (auto const *param : signature->Params()) {
if (!param->TsType()->IsETSAnyType()) {
return false;
}
}
return true;
}
if (typeToCheck->IsETSTupleType()) {
return false;
}
if (typeToCheck->IsETSArrayType() && !typeToCheck->AsETSArrayType()->IsValueArray()) {
auto const *const elementType = typeToCheck->AsETSArrayType()->ElementType();
return elementType->PossiblyETSUndefined() && IsTypeRetainedAfterErasure(elementType);
}
if (typeToCheck->IsETSUnionType()) {
auto *unionType = typeToCheck->AsETSUnionType();
if (unionType->AllOfConstituentTypes([](Type const *item) { return IsTypeRetainedAfterErasure(item); })) {
return true;
}
auto const predicate = [](Type const *item) -> bool { return IsTypeRetainedAfterErasure(item, false); };
if (unionType->AllOfConstituentTypes(predicate)) {
return unionType->AnyOfConstituentTypes([](Type const *item) -> bool { return item->IsUndefinedType(); });
}
return false;
}
return true;
}
static BinaryExpressionValidity AreTypesValidInInstanceofExpression(const ir::Expression *const right,
const Type *const rightType) noexcept
{
bool isRightTypeRetained;
if (UNLIKELY(right->IsETSUnionType())) {
const auto &unionTypeTypes = right->AsETSUnionType()->Types();
isRightTypeRetained = !std::any_of(unionTypeTypes.begin(), unionTypeTypes.end(),
[](auto *type) { return type->IsETSStringLiteralType(); });
} else {
isRightTypeRetained = !right->IsETSStringLiteralType();
}
isRightTypeRetained = isRightTypeRetained && IsTypeRetainedAfterErasure(rightType);
return isRightTypeRetained ? BinaryExpressionValidity::NO_ERR : BinaryExpressionValidity::RHS_ERR;
}
constexpr std::string_view INSTANCEOF_ALWAYS_TRUE_WORD = "true";
constexpr std::string_view INSTANCEOF_ALWAYS_FALSE_WORD = "false";
static bool HasInterfaceConstituent(Type *t)
{
if (t->IsETSUnionType()) {
return t->AsETSUnionType()->AnyOfConstituentTypes(
[](Type *ct) { return ct->IsETSObjectType() && ct->AsETSObjectType()->IsInterface(); });
}
return t->IsETSObjectType() && t->AsETSObjectType()->IsInterface();
}
static void ReportIfInstanceofTrivialKnown(ETSChecker *checker, Type *lhs, Type *rhs, lexer::SourcePosition pos)
{
if (lhs == nullptr || rhs == nullptr || lhs->IsTypeError() || rhs->IsTypeError()) {
return;
}
if (lhs == checker->GlobalETSAnyType() || rhs == checker->GlobalETSAnyType()) {
return;
}
if ((lhs->IsETSObjectType() && lhs->AsETSObjectType()->IsGradual()) ||
(rhs->IsETSObjectType() && rhs->AsETSObjectType()->IsGradual())) {
return;
}
auto *const relation = checker->Relation();
SavedTypeRelationFlagsContext const savedFlags(relation, TypeRelationFlag::IGNORE_TYPE_PARAMETERS);
auto *intersectionType = checker->Context().GetIntersectionOfTypes(lhs, rhs);
if (intersectionType != nullptr) {
if (relation->IsSupertypeOf(intersectionType, lhs)) {
checker->LogDiagnostic(diagnostic::INSTANCEOF_ALWAYS_KNOWN,
util::DiagnosticMessageParams {INSTANCEOF_ALWAYS_TRUE_WORD}, pos);
}
} else if (!HasInterfaceConstituent(lhs) || !HasInterfaceConstituent(rhs)) {
checker->LogDiagnostic(diagnostic::INSTANCEOF_ALWAYS_KNOWN,
util::DiagnosticMessageParams {INSTANCEOF_ALWAYS_FALSE_WORD}, pos);
}
}
std::tuple<Type *, Type *> ETSChecker::CheckBinaryOperatorInstanceOf(const ir::Expression *right,
checker::Type *leftType, checker::Type *rightType,
lexer::SourcePosition pos)
{
ERROR_TYPE_CHECK(this, rightType, return std::make_tuple(GlobalETSBooleanBuiltinType(), GlobalTypeError()));
const BinaryExpressionValidity exprValidity = AreTypesValidInInstanceofExpression(right, rightType);
switch (exprValidity) {
case BinaryExpressionValidity::NO_ERR: {
ReportIfInstanceofTrivialKnown(this, leftType, rightType, pos);
break;
}
case BinaryExpressionValidity::RHS_ERR: {
LogError(diagnostic::INVALID_INSTANCEOF_RHS_TYPE, {right->DumpEtsSrc()}, right->Start());
break;
}
default:
ES2PANDA_UNREACHABLE();
}
RemoveStatus(checker::CheckerStatus::IN_INSTANCEOF_CONTEXT);
return {GlobalETSBooleanBuiltinType(),
exprValidity == BinaryExpressionValidity::NO_ERR ? GlobalETSObjectType() : leftType};
}
template <typename T>
static void ConvertNumberLiteralTo(ETSChecker *checker, ir::NumberLiteral *lit, Type *toType)
{
auto &number = lit->Number();
number.SetValue(number.GetValueAndCastTo<T>());
lit->SetTsType(checker->GetConstantBuiltinType(toType));
}
template <typename From, typename To>
static bool CheckNumberLiteralValue(ETSChecker *checker, ir::NumberLiteral const *const lit)
{
auto const maxTo = static_cast<From>(std::numeric_limits<To>::max());
auto const minTo = static_cast<From>(std::numeric_limits<To>::min());
auto const val = lit->Number().GetValue<From>();
if (val < minTo || val > maxTo) {
checker->LogError(diagnostic::CONSTANT_VALUE_OUT_OF_RANGE, {}, lit->Start());
return false;
}
return true;
}
static void ConvertIntegerNumberLiteral(ETSChecker *checker, ir::NumberLiteral *lit, ETSObjectType *fromType,
ETSObjectType *toType)
{
if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG)) {
ConvertNumberLiteralTo<int64_t>(checker, lit, checker->GlobalLongBuiltinType());
} else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT)) {
if (!fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) ||
CheckNumberLiteralValue<int64_t, int32_t>(checker, lit)) {
ConvertNumberLiteralTo<int32_t>(checker, lit, checker->GlobalIntBuiltinType());
}
} else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_SHORT)) {
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) &&
!CheckNumberLiteralValue<int64_t, int16_t>(checker, lit)) {
return;
}
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT) &&
!CheckNumberLiteralValue<int32_t, int16_t>(checker, lit)) {
return;
}
ConvertNumberLiteralTo<int16_t>(checker, lit, checker->GlobalShortBuiltinType());
} else if (toType->HasObjectFlag(ETSObjectFlags::BUILTIN_BYTE)) {
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_LONG) &&
!CheckNumberLiteralValue<int64_t, int8_t>(checker, lit)) {
return;
}
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_INT) &&
!CheckNumberLiteralValue<int32_t, int8_t>(checker, lit)) {
return;
}
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_SHORT) &&
!CheckNumberLiteralValue<int16_t, int8_t>(checker, lit)) {
return;
}
ConvertNumberLiteralTo<int8_t>(checker, lit, checker->GlobalByteBuiltinType());
}
}
static void ConvertNumberLiteral(ETSChecker *checker, ir::NumberLiteral *lit, ETSObjectType *toType)
{
ES2PANDA_ASSERT(toType->IsBuiltinNumeric() && lit->TsType()->IsBuiltinNumeric());
if (auto *fromType = lit->TsType()->AsETSObjectType(); !checker->Relation()->IsIdenticalTo(fromType, toType)) {
switch (static_cast<ETSObjectFlags>(toType->ObjectFlags() & ETSObjectFlags::BUILTIN_NUMERIC)) {
case ETSObjectFlags::BUILTIN_DOUBLE:
ConvertNumberLiteralTo<double>(checker, lit, checker->GlobalDoubleBuiltinType());
break;
case ETSObjectFlags::BUILTIN_FLOAT:
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_DOUBLE)) {
checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {fromType, toType}, lit->Start());
} else {
ConvertNumberLiteralTo<float>(checker, lit, checker->GlobalFloatBuiltinType());
}
break;
case ETSObjectFlags::BUILTIN_LONG:
case ETSObjectFlags::BUILTIN_INT:
case ETSObjectFlags::BUILTIN_SHORT:
case ETSObjectFlags::BUILTIN_BYTE:
if (fromType->HasObjectFlag(ETSObjectFlags::BUILTIN_FLOATING_POINT)) {
checker->LogError(diagnostic::INVALID_ASSIGNMNENT, {fromType, toType}, lit->Start());
} else {
ConvertIntegerNumberLiteral(checker, lit, fromType, toType);
}
break;
default:
ES2PANDA_UNREACHABLE();
}
}
}
Type *ETSChecker::CheckBinaryOperatorNullishCoalescing(ir::Expression *left, ir::Expression *right,
lexer::SourcePosition pos)
{
auto *leftType = left->TsType();
if (leftType->DefinitelyNotETSNullish()) {
LogDiagnostic(diagnostic::NULLISH_COALESCING_ALWAYS_LEFT, pos);
} else if (leftType->DefinitelyETSNullish()) {
LogDiagnostic(diagnostic::NULLISH_COALESCING_ALWAYS_RIGHT, pos);
}
leftType = GetNonNullishType(leftType);
if (leftType->IsTypeError() && HasStatus(checker::CheckerStatus::IN_TYPE_INFER)) {
return GlobalTypeError();
}
ERROR_TYPE_CHECK(this, leftType, return GlobalTypeError());
if (leftType->IsETSPrimitiveType()) {
LogError(diagnostic::COALESCE_NOT_REF, {}, pos);
}
auto *rightType = MaybeBoxType(right->TsType());
if (IsTypeIdenticalTo(leftType, rightType)) {
return leftType;
}
if (right->IsNumberLiteral() && leftType->IsBuiltinNumeric()) {
ConvertNumberLiteral(this, right->AsNumberLiteral(), leftType->AsETSObjectType());
return leftType;
}
return CreateETSUnionType({leftType, rightType});
}
using CheckBinaryFunction = std::function<checker::Type *(
ETSChecker *, std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op,
bool isEqualOp, std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)>;
std::map<lexer::TokenType, CheckBinaryFunction> &GetCheckMap()
{
static std::map<lexer::TokenType, CheckBinaryFunction> checkMap = {
{lexer::TokenType::PUNCTUATOR_MULTIPLY, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_DIVIDE, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_MOD, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_MOD_EQUAL, &ETSChecker::CheckBinaryOperatorMulDivMod},
{lexer::TokenType::PUNCTUATOR_EXPONENTIATION, &ETSChecker::CheckBinaryOperatorExponentiation},
{lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL, &ETSChecker::CheckBinaryOperatorExponentiation},
{lexer::TokenType::PUNCTUATOR_MINUS, &ETSChecker::CheckBinaryOperatorPlus},
{lexer::TokenType::PUNCTUATOR_MINUS_EQUAL, &ETSChecker::CheckBinaryOperatorPlus},
{lexer::TokenType::PUNCTUATOR_PLUS, &ETSChecker::CheckBinaryOperatorPlus},
{lexer::TokenType::PUNCTUATOR_PLUS_EQUAL, &ETSChecker::CheckBinaryOperatorPlus},
{lexer::TokenType::PUNCTUATOR_LEFT_SHIFT, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL, &ETSChecker::CheckBinaryOperatorShift},
{lexer::TokenType::PUNCTUATOR_BITWISE_OR, &ETSChecker::CheckBinaryOperatorBitwise},
{lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL, &ETSChecker::CheckBinaryOperatorBitwise},
{lexer::TokenType::PUNCTUATOR_BITWISE_AND, &ETSChecker::CheckBinaryOperatorBitwise},
{lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL, &ETSChecker::CheckBinaryOperatorBitwise},
{lexer::TokenType::PUNCTUATOR_BITWISE_XOR, &ETSChecker::CheckBinaryOperatorBitwise},
{lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL, &ETSChecker::CheckBinaryOperatorBitwise},
};
return checkMap;
}
static std::tuple<Type *, Type *> CheckBinaryOperatorHelper(ETSChecker *checker,
const BinaryOperatorParams &binaryParams,
const TypeParams &typeParams)
{
ir::Expression *left = binaryParams.left;
ir::Expression *right = binaryParams.right;
lexer::SourcePosition pos = binaryParams.pos;
checker::Type *const leftType = typeParams.leftType;
checker::Type *const rightType = typeParams.rightType;
checker::Type *tsType {};
BinaryArithmOperands ops = GetBinaryOperands(checker, binaryParams.expr->AsBinaryExpression());
BinaryArithmOperands opsRepaired = RepairTypeErrorsInOperands(checker, ops);
switch (binaryParams.operationType) {
case lexer::TokenType::PUNCTUATOR_LOGICAL_AND:
case lexer::TokenType::PUNCTUATOR_LOGICAL_OR: {
tsType = checker->CheckBinaryOperatorLogical(left, right, leftType, rightType, typeParams.unboxedL,
typeParams.unboxedR);
break;
}
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL: {
if (auto res = CheckBinaryOperatorEqual(checker, opsRepaired); res != nullptr) {
bool canEmitWarning = true;
if (opsRepaired.typeL->IsETSReferenceType() && opsRepaired.typeR->IsETSReferenceType()) {
checker->Relation()->SetNode(left);
canEmitWarning = checker->CheckValidEqualReferenceType(opsRepaired.typeL, opsRepaired.typeR);
}
if (canEmitWarning) {
MaybeLogCompileTimeEqualityWarning(checker, opsRepaired, binaryParams.operationType, pos);
}
return {checker->GetGlobalTypesHolder()->GlobalETSBooleanBuiltinType(), res};
}
[[fallthrough]];
}
case lexer::TokenType::PUNCTUATOR_LESS_THAN:
case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_GREATER_THAN:
case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL: {
return checker->CheckBinaryOperatorLessGreater(left, right, binaryParams.operationType, pos,
binaryParams.isEqualOp, leftType, rightType,
typeParams.unboxedL, typeParams.unboxedR);
}
case lexer::TokenType::KEYW_INSTANCEOF: {
return checker->CheckBinaryOperatorInstanceOf(right, leftType, rightType, pos);
}
case lexer::TokenType::PUNCTUATOR_NULLISH_COALESCING: {
tsType = checker->CheckBinaryOperatorNullishCoalescing(left, right, pos);
break;
}
default: {
ES2PANDA_UNREACHABLE();
break;
}
}
return {tsType, tsType};
}
static void TryAddValueOfFlagToStringEnumOperand(ir::Expression *op, const ir::Expression *otherOp)
{
auto type = op->TsType();
auto otherType = otherOp->TsType();
if (type->IsETSStringEnumType() && (otherType->IsETSStringType() || otherType->IsETSStringEnumType())) {
op->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
}
static void TryAddValueOfFlagToNumericEnumOperand(ir::Expression *op, const ir::Expression *otherOp)
{
auto type = op->TsType();
auto otherType = otherOp->TsType();
if (type->IsETSNumericEnumType() &&
((otherType->IsETSObjectType() && otherType->AsETSObjectType()->IsBoxedPrimitive()) ||
otherType->IsETSNumericEnumType())) {
op->AddAstNodeFlags(ir::AstNodeFlags::GENERATE_VALUE_OF);
}
}
static void CheckEnumInOperatorContext(ir::Expression *expression, lexer::TokenType opType, ir::Expression *left,
ir::Expression *right, ETSChecker *checker)
{
auto [lType, rType] = std::tuple {left->TsType(), right->TsType()};
switch (opType) {
case lexer::TokenType::PUNCTUATOR_GREATER_THAN:
case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_LESS_THAN:
case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL: {
if (lType->IsETSEnumType() && rType->IsETSEnumType() && !checker->Relation()->IsIdenticalTo(lType, rType)) {
checker->LogError(diagnostic::BINOP_INCOMPARABLE, {}, expression->Start());
return;
}
[[fallthrough]];
}
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL:
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL: {
if (lType->IsETSEnumType() && rType->IsETSEnumType() && !checker->Relation()->IsIdenticalTo(lType, rType)) {
checker->LogDiagnostic(diagnostic::DIFFERENT_ENUM_EQUALITY, {}, expression->Start());
}
[[fallthrough]];
}
case lexer::TokenType::PUNCTUATOR_PLUS: {
TryAddValueOfFlagToStringEnumOperand(left, right);
TryAddValueOfFlagToStringEnumOperand(right, left);
[[fallthrough]];
}
case lexer::TokenType::PUNCTUATOR_MULTIPLY:
case lexer::TokenType::PUNCTUATOR_DIVIDE:
case lexer::TokenType::PUNCTUATOR_MOD:
case lexer::TokenType::PUNCTUATOR_MINUS:
case lexer::TokenType::PUNCTUATOR_LEFT_SHIFT:
case lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT:
case lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT:
case lexer::TokenType::PUNCTUATOR_BITWISE_AND:
case lexer::TokenType::PUNCTUATOR_BITWISE_OR:
case lexer::TokenType::PUNCTUATOR_BITWISE_XOR:
case lexer::TokenType::PUNCTUATOR_LOGICAL_AND:
case lexer::TokenType::PUNCTUATOR_LOGICAL_OR: {
TryAddValueOfFlagToNumericEnumOperand(left, right);
TryAddValueOfFlagToNumericEnumOperand(right, left);
break;
}
default:
break;
}
}
std::tuple<Type *, Type *> ETSChecker::CheckArithmeticOperations(
ir::Expression *expr, std::tuple<ir::Expression *, ir::Expression *, lexer::TokenType, lexer::SourcePosition> op,
bool isEqualOp, std::tuple<checker::Type *, checker::Type *, Type *, Type *> types)
{
auto [left, right, operationType, pos] = op;
auto [leftType, rightType, unboxedL, unboxedR] = types;
if (leftType->IsETSUnionType()) {
leftType = GetNonConstantType(leftType);
}
if (rightType->IsETSUnionType()) {
rightType = GetNonConstantType(rightType);
}
CheckEnumInOperatorContext(expr, operationType, left, right, this);
const auto &checkMap = GetCheckMap();
if (const auto &checkMapEntry = checkMap.find(operationType); checkMapEntry != checkMap.end()) {
const auto &checkFunc = checkMapEntry->second;
auto tsType = checkFunc(this, std::make_tuple(left, right, operationType, pos), isEqualOp,
std::make_tuple(leftType, rightType, unboxedL, unboxedR));
if (tsType == nullptr) {
return {leftType, rightType};
}
if (tsType->IsETSPrimitiveType()) {
tsType = MaybeBoxType(tsType);
}
return {tsType, tsType};
}
return CheckBinaryOperatorHelper(this, {left, right, expr, operationType, pos, isEqualOp},
{leftType, rightType, unboxedL, unboxedR});
}
static std::tuple<Type *, Type *> ResolveCheckBinaryOperatorForBigInt(ETSChecker *checker, Type *leftType,
Type *rightType, lexer::TokenType operationType,
lexer::SourcePosition pos)
{
if (HasBigIntNonBigIntEqualityPairing(checker, leftType, rightType)) {
switch (operationType) {
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
checker->LogDiagnostic(diagnostic::EQUALITY_EXPRESSION_ALWAYS_FALSE, pos);
break;
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL:
checker->LogDiagnostic(diagnostic::EQUALITY_EXPRESSION_ALWAYS_TRUE, pos);
break;
default:
break;
}
}
switch (operationType) {
case lexer::TokenType::PUNCTUATOR_GREATER_THAN:
case lexer::TokenType::PUNCTUATOR_LESS_THAN:
case lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL:
case lexer::TokenType::PUNCTUATOR_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_EQUAL:
case lexer::TokenType::PUNCTUATOR_STRICT_EQUAL:
case lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL:
return {checker->GlobalETSBooleanBuiltinType(), checker->GlobalETSBooleanBuiltinType()};
default:
return {leftType, rightType};
}
}
std::tuple<Type *, Type *> ETSChecker::CheckBinaryOperator(ir::Expression *left, ir::Expression *right,
ir::Expression *expr, lexer::TokenType operationType,
lexer::SourcePosition pos, bool forcePromotion)
{
checker::Type *leftType = left->Check(this);
if (leftType == nullptr) {
LogError(diagnostic::BINOP_UNEXPECTED_ERROR, {}, left->Start());
auto rightType = right->Check(this);
return {rightType, rightType};
}
if (operationType == lexer::TokenType::KEYW_INSTANCEOF) {
AddStatus(checker::CheckerStatus::IN_INSTANCEOF_CONTEXT);
}
Context().CheckTestSmartCastCondition(operationType);
checker::Type *rightType = right->Check(this);
if (right->IsTypeNode()) {
rightType = right->AsTypeNode()->GetType(this);
}
if (operationType == lexer::TokenType::KEYW_INSTANCEOF) {
RemoveStatus(checker::CheckerStatus::IN_INSTANCEOF_CONTEXT);
}
if (rightType == nullptr) {
LogError(diagnostic::BINOP_UNEXPECTED_ERROR, {}, pos);
return {leftType, leftType};
}
const bool isLogicalExtendedOperator = (operationType == lexer::TokenType::PUNCTUATOR_LOGICAL_AND) ||
(operationType == lexer::TokenType::PUNCTUATOR_LOGICAL_OR);
Type *unboxedL =
isLogicalExtendedOperator ? MaybeUnboxConditionalInRelation(leftType) : MaybeUnboxInRelation(leftType);
Type *unboxedR =
isLogicalExtendedOperator ? MaybeUnboxConditionalInRelation(rightType) : MaybeUnboxInRelation(rightType);
ES2PANDA_ASSERT(operationType != lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
bool isEqualOp = lexer::Token::IsBinaryLvalueToken(operationType) && !forcePromotion;
if ((operationType == lexer::TokenType::PUNCTUATOR_EXPONENTIATION ||
operationType == lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL) &&
IsNegativeBigIntLiteralExpression(right)) {
LogError(diagnostic::EXPONENTIATION_BIGINT_NEGATIVE_EXPONENT, {}, right->Start());
return {GlobalTypeError(), GlobalTypeError()};
}
if (leftType->IsETSBigIntType() && rightType->IsETSBigIntType() &&
operationType == lexer::TokenType::PUNCTUATOR_DIVIDE && right->IsBigIntLiteral() &&
IsBigIntZeroLiteral(right)) {
LogError(diagnostic::BIGINT_DIVISION_BY_ZERO, {}, right->Start());
return {GlobalTypeError(), GlobalTypeError()};
}
if (CheckBinaryOperatorForBigInt(leftType, rightType, operationType)) {
return ResolveCheckBinaryOperatorForBigInt(this, leftType, rightType, operationType, pos);
}
return CheckArithmeticOperations(expr, std::make_tuple(left, right, operationType, pos), isEqualOp,
std::make_tuple(leftType, rightType, unboxedL, unboxedR));
}
}