* 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 "compiler/lowering/ets/unboxLowering.h"
#include "checker/types/type.h"
#include "generated/tokenType.h"
#include "ir/visitor/IterateAstVisitor.h"
#include "checker/ETSchecker.h"
#include "checker/types/ets/etsTupleType.h"
#include "checker/types/typeFlag.h"
#include "checker/types/globalTypesHolder.h"
#include "compiler/lowering/util.h"
#include "util/es2pandaMacros.h"
#include "generated/signatures.h"
#include "util/helpers.h"
namespace ark::es2panda::compiler {
namespace {
bool AnyOfElementTypes(checker::Type *type, const std::function<bool(checker::Type *)> &isFunc)
{
auto const anyOf = [&isFunc](ArenaVector<checker::Type *> const &list) {
auto const pred = [&isFunc](checker::Type *t) { return AnyOfElementTypes(t, isFunc); };
return std::any_of(list.begin(), list.end(), pred);
};
return (type->IsETSTupleType() && anyOf(type->AsETSTupleType()->GetTupleTypesList())) ||
(type->IsETSArrayType() && isFunc(type->AsETSArrayType()->ElementType())) ||
(type->IsETSUnionType() && anyOf(type->AsETSUnionType()->ConstituentTypes())) ||
(type->IsETSObjectType() && anyOf(type->AsETSObjectType()->TypeArguments()));
}
bool IsRecursivelyUnboxed(checker::Type *t)
{
return t->IsETSPrimitiveType() || AnyOfElementTypes(t, IsRecursivelyUnboxed);
}
bool TypeIsBoxedPrimitive(checker::Type *tp)
{
return tp->IsETSObjectType() && tp->AsETSObjectType()->IsBoxedPrimitive();
}
bool IsIntrinsicConvertiblePrimitive(checker::Type *tp)
{
using checker::TypeFlag;
static constexpr TypeFlag ETS_CONVERTIBLE_PRIMITIVE =
TypeFlag::ETS_NUMERIC | TypeFlag::CHAR | TypeFlag::ETS_BOOLEAN | TypeFlag::ETS_VOID;
return tp->HasTypeFlag(ETS_CONVERTIBLE_PRIMITIVE);
}
bool IsUnboxingApplicable(checker::Type *t)
{
return TypeIsBoxedPrimitive(t) || AnyOfElementTypes(t, IsUnboxingApplicable);
}
static bool IsInAnnotationContext(ir::AstNode *node)
{
while (node != nullptr && !(node->IsClassDefinition() && node->AsClassDefinition()->IsGlobal())) {
if (node->IsAnnotationDeclaration() || node->IsAnnotationUsage()) {
return true;
}
node = node->Parent();
}
return false;
}
}
using TypeIdStorage = std::vector<std::uint64_t>;
static checker::Type *NormalizeType(UnboxContext *uctx, checker::Type *t, TypeIdStorage *alreadySeen = nullptr);
static checker::Type *MaybeRecursivelyUnboxReferenceType(UnboxContext *uctx, checker::Type *t,
TypeIdStorage *alreadySeen);
static checker::Type *MaybeRecursivelyUnboxType(UnboxContext *uctx, checker::Type *t,
TypeIdStorage *alreadySeen = nullptr)
{
if (TypeIsBoxedPrimitive(t)) {
return uctx->checker->MaybeUnboxType(t);
}
return NormalizeType(uctx, t, alreadySeen);
}
static checker::Type *MaybeRecursivelyUnboxTypeParameter(UnboxContext *uctx, checker::Type *t,
TypeIdStorage *alreadySeen)
{
if (std::find(alreadySeen->begin(), alreadySeen->end(), t->Id()) != alreadySeen->end()) {
return t;
}
alreadySeen->push_back(t->Id());
auto typeParameter = t->AsETSTypeParameter();
auto constraintType = typeParameter->GetConstraintType();
typeParameter->SetConstraintType(MaybeRecursivelyUnboxReferenceType(uctx, constraintType, alreadySeen));
return t;
}
static checker::Type *MaybeRecursivelyUnboxTupleType(UnboxContext *uctx, checker::Type *t, TypeIdStorage *alreadySeen)
{
bool anyChange = false;
auto *srcTup = t->AsETSTupleType();
std::vector<checker::Type *> newTps;
for (auto *e : srcTup->GetTupleTypesList()) {
auto *newE = MaybeRecursivelyUnboxReferenceType(uctx, e, alreadySeen);
newTps.push_back(newE);
anyChange |= (newE != e);
}
return anyChange
? uctx->checker->CreateETSTupleType(std::move(newTps), srcTup->HasTypeFlag(checker::TypeFlag::READONLY))
: t;
}
static checker::Type *MaybeRecursivelyUnboxUnionType(UnboxContext *uctx, checker::Type *t, TypeIdStorage *alreadySeen)
{
bool anyChange = false;
auto *srcUnion = t->AsETSUnionType();
std::vector<checker::Type *> newTps;
for (auto *e : srcUnion->ConstituentTypes()) {
auto *newE = MaybeRecursivelyUnboxReferenceType(uctx, e, alreadySeen);
newTps.push_back(newE);
anyChange |= (newE != e);
}
return anyChange ? uctx->checker->CreateETSUnionType(std::move(newTps)) : t;
}
static checker::Type *MaybeRecursivelyUnboxObjectType(UnboxContext *uctx, checker::Type *t, TypeIdStorage *alreadySeen)
{
bool anyChange = false;
auto *objTp = t->AsETSObjectType();
ArenaVector<checker::Type *> newTps {uctx->allocator->Adapter()};
for (auto *e : objTp->TypeArguments()) {
auto *newE = MaybeRecursivelyUnboxReferenceType(uctx, e, alreadySeen);
newTps.push_back(newE);
anyChange |= (newE != e);
}
return anyChange ? objTp->GetOriginalBaseType()->SubstituteArguments(uctx->checker->Relation(), newTps) : t;
}
static checker::Type *MaybeRecursivelyUnboxReferenceType(UnboxContext *uctx, checker::Type *t,
TypeIdStorage *alreadySeen)
{
if (t == nullptr) {
return t;
}
if (t->IsETSTypeParameter()) {
return MaybeRecursivelyUnboxTypeParameter(uctx, t, alreadySeen);
}
if (t->IsETSTupleType()) {
return MaybeRecursivelyUnboxTupleType(uctx, t, alreadySeen);
}
if (t->IsETSArrayType()) {
auto *srcArr = t->AsETSArrayType();
checker::Type *newE = nullptr;
if (srcArr->IsValueArray()) {
newE = MaybeRecursivelyUnboxType(uctx, srcArr->ElementType(), alreadySeen);
} else {
newE = MaybeRecursivelyUnboxReferenceType(uctx, srcArr->ElementType(), alreadySeen);
if (newE->IsETSPrimitiveType()) {
newE = uctx->checker->MaybeBoxType(newE);
}
}
return (newE == srcArr->ElementType()) ? t : uctx->checker->CreateETSArrayType(newE, srcArr->IsValueArray());
}
if (t->IsETSResizableArrayType()) {
auto *srcArr = t->AsETSResizableArrayType();
auto *newE = MaybeRecursivelyUnboxReferenceType(uctx, srcArr->ElementType(), alreadySeen);
return (newE == srcArr->ElementType()) ? t : uctx->checker->CreateETSResizableArrayType(newE);
}
if (t->IsETSUnionType()) {
return MaybeRecursivelyUnboxUnionType(uctx, t, alreadySeen);
}
if (t->IsETSObjectType()) {
return MaybeRecursivelyUnboxObjectType(uctx, t, alreadySeen);
}
return t;
}
static checker::Type *NormalizeType(UnboxContext *uctx, checker::Type *tp, TypeIdStorage *alreadySeen)
{
if (alreadySeen == nullptr) {
TypeIdStorage newAlreadySeen {};
return MaybeRecursivelyUnboxReferenceType(uctx, tp, &newAlreadySeen);
}
return MaybeRecursivelyUnboxReferenceType(uctx, tp, alreadySeen);
}
static void NormalizeAllTypes(UnboxContext *uctx, ir::AstNode *ast)
{
ast->TransformChildrenRecursivelyPreorder(
[uctx](ir::AstNode *child) -> ir::AstNode* {
if (child->IsExpression() && child->AsExpression()->IsTypeNode()) {
if (child->Parent()->IsAnnotationUsage() || child->IsETSTypeReferencePart()) {
return child;
}
auto typeNodeType = child->AsExpression()->AsTypeNode()->GetType(uctx->checker);
if (typeNodeType == nullptr || typeNodeType->IsETSAnyType()) {
return child;
}
auto r = uctx->allocator->New<ir::OpaqueTypeNode>(NormalizeType(uctx, typeNodeType),
uctx->allocator);
r->SetRange(child->Range());
r->SetParent(child->Parent());
return r;
}
if (child->IsTyped()) {
child->AsTyped()->SetTsType(NormalizeType(uctx, child->AsTyped()->TsType()));
if (child->Variable() != nullptr && child->Variable()->TsType() != nullptr) {
child->Variable()->SetTsType(NormalizeType(uctx, child->Variable()->TsType()));
}
}
return child;
},
"unbox-normalize-types");
}
static void HandleScriptFunctionHeader(UnboxContext *uctx, ir::ScriptFunction *func)
{
if (func->Language() == Language::Id::JS) {
return;
}
auto *sig = func->Signature();
if (sig == nullptr) {
return;
}
if (func->Parent()->Parent()->IsMethodDefinition() &&
func->Parent()->Parent()->AsMethodDefinition()->Id()->Name() == "valueOf" &&
util::Helpers::GetContainingObjectType(func)->AsETSObjectType()->IsBoxedPrimitive() &&
sig->Params().size() == 1) {
auto *boxed = func->Parent()->Parent()->Parent()->AsTyped()->TsType();
auto *unboxed = MaybeRecursivelyUnboxType(uctx, boxed);
ES2PANDA_ASSERT(sig->ReturnType() == boxed);
sig->Params()[0]->SetTsType(unboxed);
uctx->varbinder->BuildFunctionName(func);
return;
}
for (size_t i = 0; i < func->Signature()->Params().size(); i++) {
auto *sigParam = func->Signature()->Params()[i];
auto *funcParam = func->Params()[i]->AsETSParameterExpression();
if (IsUnboxingApplicable(sigParam->TsType())) {
auto *unboxedType = MaybeRecursivelyUnboxType(uctx, sigParam->TsType());
sigParam->SetTsType(unboxedType);
funcParam->SetTsType(unboxedType);
funcParam->Ident()->SetTsType(unboxedType);
funcParam->Variable()->SetTsType(unboxedType);
}
}
if (sig->RestVar() != nullptr) {
auto *funcRestParam = func->Params()[func->Params().size() - 1]->AsETSParameterExpression();
ES2PANDA_ASSERT(funcRestParam != nullptr && funcRestParam->IsRestParameter());
auto *unboxedType = MaybeRecursivelyUnboxType(uctx, sig->RestVar()->TsType());
sig->RestVar()->SetTsType(unboxedType);
funcRestParam->Ident()->SetTsType(unboxedType);
funcRestParam->Ident()->Variable()->SetTsType(unboxedType);
}
const bool hasUndefinedReturnType = sig->ReturnType() != nullptr && sig->ReturnType()->IsETSUndefinedType();
if (hasUndefinedReturnType) {
sig->SetReturnType(uctx->checker->GlobalVoidType());
} else if (IsUnboxingApplicable(sig->ReturnType())) {
sig->SetReturnType(MaybeRecursivelyUnboxType(uctx, sig->ReturnType()));
}
uctx->varbinder->BuildFunctionName(func);
}
template <bool IN_ANNOTATION = false>
void HandleClassProperty(UnboxContext *uctx, ir::ClassProperty *prop)
{
ir::AstNode *node = prop;
while (node != nullptr && !node->IsETSModule()) {
node = node->Parent();
}
if (node != nullptr && node->AsETSModule()->Program()->IsDeclForDynamicStaticInterop() && !IN_ANNOTATION) {
return;
}
checker::Type *propType = prop->TsType();
if (propType == nullptr) {
propType = prop->Key()->Variable()->TsType();
}
if (propType == nullptr) {
return;
}
if (IsUnboxingApplicable(propType) && prop->Key()->IsIdentifier()) {
auto *unboxedType = MaybeRecursivelyUnboxType(uctx, propType);
prop->SetTsType(unboxedType);
prop->Key()->Variable()->SetTsType(unboxedType);
}
}
static void HandleDeclarationNode(UnboxContext *uctx, ir::AstNode *ast);
static ir::ClassDefinition *GetDirectOwnerClassDefinition(ir::ClassProperty *prop)
{
auto *parent = prop->Parent();
return parent != nullptr && parent->IsClassDefinition() ? parent->AsClassDefinition() : nullptr;
}
static bool IsPartialClassDefinition(const ir::ClassDefinition *classDefinition)
{
if (classDefinition == nullptr || classDefinition->TsType() == nullptr ||
!classDefinition->TsType()->IsETSObjectType()) {
return false;
}
return classDefinition->TsType()->AsETSObjectType()->IsPartial();
}
static void HandleBaseClassProperty(UnboxContext *uctx, ir::ClassProperty *prop)
{
if (!prop->Key()->IsIdentifier()) {
return;
}
auto *classDefinition = GetDirectOwnerClassDefinition(prop);
if (classDefinition == nullptr || IsPartialClassDefinition(classDefinition) ||
classDefinition->TsType() == nullptr || !classDefinition->TsType()->IsETSObjectType()) {
return;
}
auto *superType = classDefinition->TsType()->AsETSObjectType()->SuperType();
if (superType == nullptr) {
return;
}
auto searchFlag = prop->IsStatic() ? checker::PropertySearchFlags::SEARCH_STATIC_FIELD |
checker::PropertySearchFlags::SEARCH_STATIC_DECL |
checker::PropertySearchFlags::SEARCH_IN_BASE
: checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD |
checker::PropertySearchFlags::SEARCH_INSTANCE_DECL |
checker::PropertySearchFlags::SEARCH_IN_BASE;
auto *basePropVar = superType->GetProperty(prop->Key()->AsIdentifier()->Name(), searchFlag);
if (basePropVar == nullptr || basePropVar->Declaration() == nullptr ||
basePropVar->Declaration()->Node() == nullptr || !basePropVar->Declaration()->Node()->IsClassProperty()) {
return;
}
auto *baseProp = basePropVar->Declaration()->Node()->AsClassProperty();
auto *baseClassDefinition = GetDirectOwnerClassDefinition(baseProp);
if (baseClassDefinition != nullptr && !IsPartialClassDefinition(baseClassDefinition)) {
HandleDeclarationNode(uctx, baseProp);
}
}
static void HandleVariableDeclarator(UnboxContext *uctx, ir::VariableDeclarator *vdecl)
{
if (IsUnboxingApplicable(vdecl->Id()->Variable()->TsType())) {
auto *unboxedType = MaybeRecursivelyUnboxType(uctx, vdecl->Id()->Variable()->TsType());
vdecl->SetTsType(unboxedType);
vdecl->Id()->SetTsType(unboxedType);
vdecl->Id()->Variable()->SetTsType(unboxedType);
}
}
static void HandleDeclarationNode(UnboxContext *uctx, ir::AstNode *ast)
{
if (uctx->handled.count(ast) > 0) {
return;
}
if (ast->IsScriptFunction()) {
HandleScriptFunctionHeader(uctx, ast->AsScriptFunction());
} else if (ast->IsMethodDefinition()) {
HandleScriptFunctionHeader(uctx, ast->AsMethodDefinition()->Function());
} else if (ast->IsClassProperty()) {
HandleClassProperty(uctx, ast->AsClassProperty());
HandleBaseClassProperty(uctx, ast->AsClassProperty());
} else if (ast->IsVariableDeclarator()) {
HandleVariableDeclarator(uctx, ast->AsVariableDeclarator());
}
uctx->handled.insert(ast);
}
std::string_view GetUnboxerMethodName(const checker::Type *unboxedType)
{
if (unboxedType->IsETSBooleanType()) {
return Signatures::BOOLEAN_CAST;
}
if (unboxedType->IsByteType()) {
return Signatures::BYTE_CAST;
}
if (unboxedType->IsETSCharType() || unboxedType->IsCharType()) {
return Signatures::CHAR_CAST;
}
if (unboxedType->IsDoubleType()) {
return Signatures::DOUBLE_CAST;
}
if (unboxedType->IsFloatType()) {
return Signatures::FLOAT_CAST;
}
if (unboxedType->IsIntType()) {
return Signatures::INT_CAST;
}
if (unboxedType->IsLongType()) {
return Signatures::LONG_CAST;
}
if (unboxedType->IsShortType()) {
return Signatures::SHORT_CAST;
}
ES2PANDA_UNREACHABLE();
}
static ir::Expression *InsertUnboxing(UnboxContext *uctx, ir::Expression *expr)
{
auto *boxedType = expr->TsType();
if (boxedType->IsETSTypeParameter()) {
boxedType = boxedType->AsETSTypeParameter()->GetConstraintType();
}
auto *unboxedType = MaybeRecursivelyUnboxType(uctx, boxedType);
auto *parent = expr->Parent();
auto *allocator = uctx->allocator;
if (expr->IsETSNewClassInstanceExpression() &&
expr->AsETSNewClassInstanceExpression()->GetArguments().size() == 1 &&
uctx->checker->Relation()->IsIdenticalTo(expr->AsETSNewClassInstanceExpression()->GetArguments()[0]->TsType(),
unboxedType)) {
auto *ret = expr->AsETSNewClassInstanceExpression()->GetArguments()[0];
ret->SetParent(parent);
return ret;
}
const std::string_view unboxerName = GetUnboxerMethodName(unboxedType);
auto *methodId = allocator->New<ir::Identifier>(unboxerName, allocator);
auto *mexpr = util::NodeAllocator::ForceSetParent<ir::MemberExpression>(
allocator, expr, methodId, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
auto *call = util::NodeAllocator::ForceSetParent<ir::CallExpression>(
allocator, mexpr, ArenaVector<ir::Expression *>(allocator->Adapter()), nullptr, false);
call->SetParent(parent);
BindLoweredNode(uctx->varbinder, call);
auto *methodVar = &*boxedType->AsETSObjectType()->InstanceMethods()[methodId->Name()];
methodId->SetVariable(methodVar);
auto *calleeMethod = methodVar->Declaration()->Node();
HandleDeclarationNode(uctx, calleeMethod);
mexpr->SetTsType(methodVar->TsType());
mexpr->SetObjectType(boxedType->AsETSObjectType());
call->SetTsType(unboxedType);
call->SetSignature(methodVar->TsType()->AsETSFunctionType()->CallSignatures()[0]);
return call;
}
static ir::Expression *CreateToIntrinsicCallExpression(UnboxContext *uctx, checker::Type *toType,
checker::Type *exprType, ir::Expression *expr)
{
auto *allocator = uctx->allocator;
ES2PANDA_ASSERT(IsIntrinsicConvertiblePrimitive(exprType));
auto *parent = expr->Parent();
auto *boxedToType = uctx->checker->MaybeBoxType(toType)->AsETSObjectType();
auto *boxedExprType = uctx->checker->MaybeBoxType(exprType)->AsETSObjectType();
auto args = ArenaVector<ir::Expression *>(allocator->Adapter());
auto name = util::UString("to" + boxedToType->ToStringAsSrc(), allocator).View();
auto *memberExpr = util::NodeAllocator::ForceSetParent<ir::MemberExpression>(
allocator, allocator->New<ir::OpaqueTypeNode>(boxedExprType, allocator),
allocator->New<ir::Identifier>(name, allocator), ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
args.push_back(expr);
auto *call =
util::NodeAllocator::ForceSetParent<ir::CallExpression>(allocator, memberExpr, std::move(args), nullptr, false);
call->SetParent(parent);
BindLoweredNode(uctx->varbinder, call);
auto *methodVar = &*boxedExprType->StaticMethods()[name];
memberExpr->Property()->SetVariable(methodVar);
auto *calleeMethod = methodVar->Declaration()->Node();
HandleDeclarationNode(uctx, calleeMethod);
memberExpr->SetTsType(methodVar->TsType());
memberExpr->SetObjectType(boxedExprType);
call->SetTsType(toType);
call->SetSignature(methodVar->TsType()->AsETSFunctionType()->CallSignatures()[0]);
return call;
}
static bool CheckIfOnTopOfUnboxing(UnboxContext *uctx, ir::Expression *expr, checker::Type *boxedType)
{
constexpr std::array<std::string_view, 8> UNBOXER_METHOD_NAMES {
Signatures::BOOLEAN_CAST, Signatures::BYTE_CAST, Signatures::CHAR_CAST, Signatures::DOUBLE_CAST,
Signatures::FLOAT_CAST, Signatures::INT_CAST, Signatures::LONG_CAST, Signatures::SHORT_CAST,
};
if (expr->IsCallExpression() && expr->AsCallExpression()->Arguments().empty() &&
expr->AsCallExpression()->Callee()->IsMemberExpression() &&
expr->AsCallExpression()->Callee()->AsMemberExpression()->Property()->IsIdentifier()) {
const util::StringView &name =
expr->AsCallExpression()->Callee()->AsMemberExpression()->Property()->AsIdentifier()->Name();
return UNBOXER_METHOD_NAMES.cend() != std::find_if(UNBOXER_METHOD_NAMES.cbegin(), UNBOXER_METHOD_NAMES.cend(),
[&name](const auto &hay) { return name.Is(hay); }) &&
uctx->checker->Relation()->IsIdenticalTo(
expr->AsCallExpression()->Callee()->AsMemberExpression()->Object()->TsType(), boxedType);
}
return false;
}
static ir::Expression *LinkUnboxingExpr(ir::Expression *expr, ir::AstNode *parent)
{
auto *ret = expr->AsCallExpression()->Callee()->AsMemberExpression()->Object();
ret->SetParent(parent);
return ret;
}
static ir::Expression *InsertBoxing(UnboxContext *uctx, ir::Expression *expr)
{
auto *unboxedType = expr->TsType();
auto *boxedType = uctx->checker->MaybeBoxType(unboxedType);
auto *parent = expr->Parent();
if (CheckIfOnTopOfUnboxing(uctx, expr, boxedType)) {
return LinkUnboxingExpr(expr, parent);
}
auto *allocator = uctx->allocator;
auto args = ArenaVector<ir::Expression *>(allocator->Adapter());
args.push_back(expr);
auto *constrCall = util::NodeAllocator::ForceSetParent<ir::ETSNewClassInstanceExpression>(
allocator, allocator->New<ir::OpaqueTypeNode>(boxedType, allocator), std::move(args));
constrCall->SetParent(parent);
auto &constructSignatures = boxedType->AsETSObjectType()->ConstructSignatures();
checker::Signature *signature = nullptr;
for (auto *sig : constructSignatures) {
if (sig->Params().size() == 1 && sig->Params()[0]->TsType() == unboxedType) {
signature = sig;
break;
}
}
ES2PANDA_ASSERT(signature != nullptr);
auto *constructor = signature->Function();
HandleDeclarationNode(uctx, constructor);
constrCall->SetTsType(boxedType);
constrCall->SetSignature(signature);
return constrCall;
}
static checker::Type *SelectTypeToConvert(
std::tuple<UnboxContext *, checker::TypeRelation *, checker::ETSChecker *> ctx, checker::Type *toConvert,
checker::Type *expectedType, checker::Type *actualType)
{
auto [uctx, relation, checker] = ctx;
if (toConvert == nullptr && actualType->IsCharType() &&
relation->IsSupertypeOf(expectedType, checker->GlobalBuiltinETSStringType())) {
return uctx->checker->GlobalBuiltinETSStringType();
}
if (toConvert == nullptr && actualType->IsByteType() &&
relation->IsSupertypeOf(expectedType, checker->GlobalCharBuiltinType())) {
return uctx->checker->GlobalCharBuiltinType();
}
if (toConvert == nullptr && actualType->IsFloatType() &&
relation->IsSupertypeOf(expectedType, checker->GlobalIntBuiltinType())) {
return checker->GlobalIntBuiltinType();
}
if (toConvert == nullptr && actualType->IsDoubleType() &&
relation->IsSupertypeOf(expectedType, checker->GlobalLongBuiltinType())) {
return checker->GlobalLongBuiltinType();
}
return toConvert;
}
static checker::Type *DetermineTypeToConvert(UnboxContext *uctx, checker::Type *actualType, checker::Type *expectedType)
{
auto *checker = uctx->checker;
checker::Type *toConvert = nullptr;
auto checkSubtyping = [expectedType, checker, &toConvert](checker::Type *tp) {
if (toConvert != nullptr) {
return;
}
if (checker->Relation()->IsSupertypeOf(expectedType, checker->MaybeBoxType(tp))) {
toConvert = tp;
}
};
switch (checker->ETSType(MaybeRecursivelyUnboxType(uctx, actualType))) {
case checker::TypeFlag::BYTE:
checkSubtyping(checker->GlobalByteBuiltinType());
[[fallthrough]];
case checker::TypeFlag::SHORT:
checkSubtyping(checker->GlobalShortBuiltinType());
[[fallthrough]];
case checker::TypeFlag::CHAR:
case checker::TypeFlag::INT:
checkSubtyping(checker->GlobalIntBuiltinType());
[[fallthrough]];
case checker::TypeFlag::LONG:
checkSubtyping(checker->GlobalLongBuiltinType());
[[fallthrough]];
case checker::TypeFlag::FLOAT:
checkSubtyping(checker->GlobalFloatBuiltinType());
[[fallthrough]];
case checker::TypeFlag::DOUBLE:
checkSubtyping(checker->GlobalDoubleBuiltinType());
[[fallthrough]];
default:
break;
}
toConvert =
SelectTypeToConvert(std::make_tuple(uctx, checker->Relation(), checker), toConvert, expectedType, actualType);
return toConvert;
}
static ir::Expression *InsertPrimitiveConversionIfNeeded(UnboxContext *uctx, ir::Expression *expr,
checker::Type *expectedType)
{
auto *checker = uctx->checker;
auto *relation = checker->Relation();
auto *actualType = expr->TsType();
ES2PANDA_ASSERT(IsRecursivelyUnboxed(actualType));
if (relation->IsSupertypeOf(expectedType, uctx->checker->MaybeBoxType(actualType))) {
return expr;
}
if (!IsIntrinsicConvertiblePrimitive(actualType)) {
return expr;
}
auto *toConvert = DetermineTypeToConvert(uctx, actualType, expectedType);
if (toConvert == nullptr) {
return expr;
}
auto *toConvertUnboxed = checker->MaybeUnboxType(toConvert);
auto *res = CreateToIntrinsicCallExpression(uctx, toConvertUnboxed, actualType, expr);
auto range = expr->Range();
SetSourceRangesRecursively(res, range);
res->SetRange(range);
return res;
}
static ir::Expression *PerformLiteralConversion(UnboxContext *uctx, lexer::Number const &n, checker::Type *expectedType)
{
auto *allocator = uctx->allocator;
bool isInt = false;
int64_t longValue = 0;
double doubleValue = 0.0;
if (n.IsByte()) {
longValue = n.GetByte();
isInt = true;
} else if (n.IsShort()) {
longValue = n.GetShort();
isInt = true;
} else if (n.IsInt()) {
longValue = n.GetInt();
isInt = true;
} else if (n.IsLong()) {
longValue = n.GetLong();
isInt = true;
} else if (n.IsFloat()) {
doubleValue = n.GetFloat();
isInt = false;
} else if (n.IsDouble()) {
doubleValue = n.GetDouble();
isInt = false;
} else {
ES2PANDA_UNREACHABLE();
}
lexer::Number num {};
if (expectedType->IsByteType()) {
num = lexer::Number {isInt ? (int8_t)longValue : (int8_t)doubleValue};
} else if (expectedType->IsShortType()) {
num = lexer::Number {isInt ? (int16_t)longValue : (int16_t)doubleValue};
} else if (expectedType->IsIntType()) {
num = lexer::Number {isInt ? (int32_t)longValue : (int32_t)doubleValue};
} else if (expectedType->IsLongType()) {
num = lexer::Number {isInt ? longValue : (int64_t)doubleValue};
} else if (expectedType->IsFloatType()) {
num = lexer::Number {isInt ? (float)longValue : (float)doubleValue};
} else if (expectedType->IsDoubleType()) {
num = lexer::Number {isInt ? (double)longValue : doubleValue};
} else {
ES2PANDA_UNREACHABLE();
}
auto *res = allocator->New<ir::NumberLiteral>(num);
res->SetTsType(expectedType);
return res;
}
static ir::Expression *InsertConversionBetweenPrimitivesIfNeeded(UnboxContext *uctx, ir::Expression *expr,
checker::Type *expectedType)
{
auto *oldType = expr->TsType();
if (uctx->checker->Relation()->IsIdenticalTo(oldType, expectedType)) {
return expr;
}
if (!IsIntrinsicConvertiblePrimitive(oldType)) {
return expr;
}
auto *parent = expr->Parent();
ir::Expression *res;
auto range = expr->Range();
if (expr->IsNumberLiteral() && expectedType->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC)) {
res = PerformLiteralConversion(uctx, expr->AsNumberLiteral()->Number(), expectedType);
res->SetRange(range);
} else if (expr->IsCharLiteral() && expectedType->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC)) {
res = PerformLiteralConversion(uctx, lexer::Number {static_cast<int32_t>(expr->AsCharLiteral()->Char())},
expectedType);
res->SetRange(range);
} else {
res = CreateToIntrinsicCallExpression(uctx, expectedType, oldType, expr);
SetSourceRangesRecursively(res, range);
}
res->SetParent(parent);
res->SetTsType(expectedType);
return res;
}
static ir::Expression *InsertReferenceCastIfNeeded(UnboxContext *uctx, ir::Expression *expr,
checker::Type *expectedType)
{
if (uctx->checker->Relation()->IsSupertypeOf(expectedType, expr->TsType())) {
return expr;
}
auto *allocator = uctx->allocator;
auto *parent = expr->Parent();
auto range = expr->Range();
auto *asExpr = util::NodeAllocator::ForceSetParent<ir::TSAsExpression>(
allocator, expr, allocator->New<ir::OpaqueTypeNode>(expectedType, allocator), false);
asExpr->SetParent(parent);
asExpr->SetTsType(expectedType);
asExpr->TypeAnnotation()->SetTsType(expectedType);
asExpr->TypeAnnotation()->SetRange(range);
asExpr->SetRange(range);
return asExpr;
}
static ir::Expression *AdjustType(UnboxContext *uctx, ir::Expression *expr, checker::Type *expectedType)
{
if (expr == nullptr) {
return nullptr;
}
ES2PANDA_ASSERT(expectedType != nullptr);
expectedType = uctx->checker->GetApparentType(expectedType);
checker::Type *actualType = expr->Check(uctx->checker);
if (expectedType->HasTypeFlag(checker::TypeFlag::ETS_NEVER)) {
return expr;
}
if (actualType->IsETSVoidType()) {
return expr;
}
if (IsInAnnotationContext(expr) && actualType->IsETSPrimitiveType() && TypeIsBoxedPrimitive(expectedType)) {
checker::Type *primitiveType = uctx->checker->MaybeUnboxType(expectedType);
return InsertConversionBetweenPrimitivesIfNeeded(uctx, expr, primitiveType);
}
if (actualType->IsETSPrimitiveType() && checker::ETSChecker::IsReferenceType(expectedType)) {
expr = InsertPrimitiveConversionIfNeeded(uctx, expr, expectedType);
return InsertReferenceCastIfNeeded(uctx, InsertBoxing(uctx, expr), expectedType);
}
if ((TypeIsBoxedPrimitive(actualType) ||
(actualType->IsETSTypeParameter() &&
TypeIsBoxedPrimitive(actualType->AsETSTypeParameter()->GetConstraintType()))) &&
expectedType->IsETSPrimitiveType()) {
return InsertConversionBetweenPrimitivesIfNeeded(uctx, InsertUnboxing(uctx, expr), expectedType);
}
if (TypeIsBoxedPrimitive(actualType) && checker::ETSChecker::IsReferenceType(expectedType) &&
!uctx->checker->Relation()->IsSupertypeOf(expectedType, actualType)) {
return AdjustType(uctx, InsertUnboxing(uctx, expr), expectedType);
}
if (actualType->IsETSPrimitiveType() && expectedType->IsETSPrimitiveType()) {
return InsertConversionBetweenPrimitivesIfNeeded(uctx, expr, expectedType);
}
return expr;
}
static void BoxDynamicOrJsArgs(UnboxContext *uctx, ArenaVector<ir::Expression *> &arguments)
{
for (size_t i = 0; i < arguments.size(); i++) {
auto *arg = arguments[i];
arguments[i] = AdjustType(uctx, arg, uctx->checker->MaybeBoxType(arg->TsType()));
}
}
static void AdjustCallArgsForCallee(UnboxContext *uctx, ir::ScriptFunction *func, checker::Signature *callSig,
ArenaVector<ir::Expression *> &arguments)
{
for (size_t i = 0; i < arguments.size(); i++) {
auto *arg = arguments[i];
if (i >= func->Signature()->Params().size()) {
auto *restVar = callSig->RestVar();
if (restVar != nullptr &&
!arg->IsSpreadElement()) {
auto *restElemType = uctx->checker->GetElementTypeOfArray(restVar->TsType());
arguments[i] = AdjustType(uctx, arg, restElemType);
}
} else {
auto *origSigType = func->Signature()->Params()[i]->TsType();
if (origSigType->IsETSPrimitiveType()) {
callSig->Params()[i]->SetTsType(origSigType);
arguments[i] = AdjustType(uctx, arg, origSigType);
} else {
arguments[i] = AdjustType(uctx, arg, callSig->Params()[i]->TsType());
}
}
}
}
static void NormalizeCallSigReturn(UnboxContext *uctx, checker::Signature *callSig, ir::ScriptFunction *func)
{
if (func->Signature()->ReturnType()->IsETSPrimitiveType()) {
callSig->SetReturnType(func->Signature()->ReturnType());
} else {
callSig->SetReturnType(NormalizeType(uctx, callSig->ReturnType()));
}
}
static void SetThisReturnCallType(UnboxContext *uctx, ir::CallExpression *call)
{
auto *callee = call->Callee();
auto isFuncRefCall = [&callee]() {
if (!callee->IsMemberExpression()) {
return false;
};
auto *calleeObject = callee->AsMemberExpression()->Object();
return (calleeObject)
->TsType()
->IsETSFunctionType() ||
(calleeObject->TsType()->IsETSObjectType() &&
calleeObject->TsType()->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::FUNCTIONAL));
}();
if (callee->IsMemberExpression() && !isFuncRefCall) {
auto *obj = callee->AsMemberExpression()->Object();
if (obj->IsSuperExpression()) {
auto *derived = uctx->checker->Context().ContainingClass();
call->SetTsType(derived != nullptr ? static_cast<checker::Type *>(derived) : obj->TsType());
} else {
call->SetTsType(obj->TsType());
}
} else {
ES2PANDA_ASSERT(!call->Arguments().empty());
call->SetTsType(call->Arguments()[0]->TsType());
}
}
static void HandleForOfStatement([[maybe_unused]] UnboxContext *uctx, [[maybe_unused]] ir::ForOfStatement *forOf)
{
ES2PANDA_UNREACHABLE();
}
static checker::Type *EffectiveTypeOfNumericOrEqualsOp(checker::ETSChecker *checker, checker::Type *left,
checker::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();
}
if (left->IsETSBooleanType() && right->IsETSBooleanType()) {
return checker->GlobalETSBooleanType();
}
return checker->GlobalIntType();
}
static void ReplaceInParent(ir::AstNode *from, ir::AstNode *to)
{
auto const replaceNode = [=](ir::AstNode *child) -> ir::AstNode* {
if (child == from) {
to->SetParent(from->Parent());
return to;
}
return child;
};
from->Parent()->TransformChildren(replaceNode, "UnboxLoweringReplaceInParent");
}
namespace {
struct UnboxVisitor : public ir::visitor::EmptyAstVisitor {
explicit UnboxVisitor(UnboxContext *uctx) : uctx_(uctx) {}
inline checker::Type *GetArrayElementType(checker::Type *arrType)
{
return uctx_->checker->GetElementTypeOfArray(arrType);
}
void VisitReturnStatement(ir::ReturnStatement *retStmt) override
{
ir::ScriptFunction *nearestScriptFunction = nullptr;
for (ir::AstNode *curr = retStmt; curr != nullptr; curr = curr->Parent()) {
if (curr->IsScriptFunction()) {
nearestScriptFunction = curr->AsScriptFunction();
break;
}
}
if (nearestScriptFunction != nullptr && retStmt != nullptr) {
retStmt->SetArgument(
AdjustType(uctx_, retStmt->Argument(), nearestScriptFunction->Signature()->ReturnType()));
}
}
void VisitIfStatement(ir::IfStatement *ifStmt) override
{
if (TypeIsBoxedPrimitive(ifStmt->Test()->TsType())) {
ifStmt->SetTest(InsertUnboxing(uctx_, ifStmt->Test()));
}
}
void VisitWhileStatement(ir::WhileStatement *whStmt) override
{
if (TypeIsBoxedPrimitive(whStmt->Test()->TsType())) {
whStmt->SetTest(InsertUnboxing(uctx_, whStmt->Test()));
}
}
void VisitSwitchStatement(ir::SwitchStatement *swtch) override
{
auto *discType = uctx_->checker->MaybeUnboxType(swtch->Discriminant()->TsType());
if (discType->IsETSStringType()) {
return;
}
swtch->SetDiscriminant(AdjustType(uctx_, swtch->Discriminant(), discType));
for (auto *scase : swtch->Cases()) {
scase->SetTest(AdjustType(uctx_, scase->Test(), discType));
}
}
void VisitCallExpression(ir::CallExpression *call) override
{
if (!call->Signature()->HasFunction() || call->Signature()->Function()->Language() == Language::Id::JS) {
BoxDynamicOrJsArgs(uctx_, call->Arguments());
return;
}
auto *func = call->Signature()->Function();
HandleDeclarationNode(uctx_, func);
AdjustCallArgsForCallee(uctx_, func, call->Signature(), call->Arguments());
NormalizeCallSigReturn(uctx_, call->Signature(), func);
if (call->Signature()->HasSignatureFlag(checker::SignatureFlags::THIS_RETURN_TYPE)) {
SetThisReturnCallType(uctx_, call);
} else if (auto *returnType = call->Signature()->ReturnType(); returnType->IsETSPrimitiveType()) {
call->SetTsType(returnType);
}
}
void VisitETSNewClassInstanceExpression(ir::ETSNewClassInstanceExpression *call) override
{
auto *func = call->Signature()->Function();
if (func == nullptr || func->Language() == Language::Id::JS) {
BoxDynamicOrJsArgs(uctx_, call->GetArguments());
return;
}
HandleDeclarationNode(uctx_, func);
AdjustCallArgsForCallee(uctx_, func, call->Signature(), call->GetArguments());
call->SetTsType(call->GetTypeRef()->TsType());
}
void VisitETSIntrinsicNode(ir::ETSIntrinsicNode *intrin) override
{
for (size_t i = 0; i < intrin->Arguments().size(); i++) {
auto arg = intrin->Arguments()[i];
auto expectedType = intrin->ExpectedTypeAt(uctx_->checker, i);
expectedType = expectedType == nullptr ? uctx_->checker->GlobalETSAnyType() : expectedType;
if (expectedType->IsETSPrimitiveType()) {
intrin->Arguments()[i] = AdjustType(uctx_, arg, expectedType);
} else {
intrin->Arguments()[i] = AdjustType(uctx_, arg, expectedType);
}
}
}
void VisitSpreadElement(ir::SpreadElement *spread) override
{
spread->SetTsType(spread->Argument()->TsType());
}
void VisitArrayExpression(ir::ArrayExpression *aexpr) override
{
auto *unboxedType = MaybeRecursivelyUnboxType(uctx_, aexpr->TsType());
aexpr->SetTsType(unboxedType);
for (size_t i = 0; i < aexpr->Elements().size(); i++) {
checker::Type *expectedType;
if (aexpr->TsType()->IsETSTupleType()) {
expectedType = aexpr->TsType()->AsETSTupleType()->GetTypeAtIndex(i);
} else if (aexpr->TsType()->IsETSArrayType() || aexpr->TsType()->IsETSResizableArrayType()) {
expectedType = GetArrayElementType(aexpr->TsType());
} else {
ES2PANDA_UNREACHABLE();
}
aexpr->Elements()[i] = AdjustType(uctx_, aexpr->Elements()[i], expectedType);
}
}
void HandleArithmeticLike(ir::BinaryExpression *bexpr)
{
bexpr->SetTsType(uctx_->checker->MaybeUnboxType(bexpr->TsType()));
bexpr->SetOperationType(uctx_->checker->MaybeUnboxType(bexpr->OperationType()));
if (TypeIsBoxedPrimitive(bexpr->Left()->TsType())) {
bexpr->SetLeft(InsertUnboxing(uctx_, bexpr->Left()));
}
if (TypeIsBoxedPrimitive(bexpr->Right()->TsType())) {
bexpr->SetRight(InsertUnboxing(uctx_, bexpr->Right()));
}
}
void HandleEqualityOrInequality(ir::BinaryExpression *bexpr)
{
auto *leftTp = bexpr->Left()->TsType();
auto *rightTp = bexpr->Right()->TsType();
checker::Type *opType = nullptr;
if ((leftTp->IsETSPrimitiveType() || TypeIsBoxedPrimitive(leftTp)) &&
(rightTp->IsETSPrimitiveType() || TypeIsBoxedPrimitive(rightTp))) {
auto *newLeftTp = uctx_->checker->MaybeUnboxType(leftTp);
auto *newRightTp = uctx_->checker->MaybeUnboxType(rightTp);
bexpr->SetLeft(AdjustType(uctx_, bexpr->Left(), newLeftTp));
bexpr->SetRight(AdjustType(uctx_, bexpr->Right(), newRightTp));
opType = EffectiveTypeOfNumericOrEqualsOp(uctx_->checker, newLeftTp, newRightTp);
bexpr->SetLeft(InsertConversionBetweenPrimitivesIfNeeded(uctx_, bexpr->Left(), opType));
bexpr->SetRight(InsertConversionBetweenPrimitivesIfNeeded(uctx_, bexpr->Right(), opType));
} else {
bexpr->SetLeft(AdjustType(uctx_, bexpr->Left(), uctx_->checker->MaybeBoxType(leftTp)));
bexpr->SetRight(AdjustType(uctx_, bexpr->Right(), uctx_->checker->MaybeBoxType(rightTp)));
opType = bexpr->OperationType();
}
bexpr->SetOperationType(opType);
bexpr->SetTsType(uctx_->checker->GlobalETSBooleanType());
}
void HandleLogical(ir::BinaryExpression *bexpr)
{
auto *leftType = bexpr->Left()->TsType();
auto *rightType = bexpr->Right()->TsType();
if (uctx_->checker->Relation()->IsIdenticalTo(leftType, rightType)) {
bexpr->SetTsType(leftType);
bexpr->SetOperationType(leftType);
} else {
auto *oldLeft = bexpr->Left();
auto *oldRight = bexpr->Right();
auto *leftBoxed = uctx_->checker->MaybeBoxType(leftType);
auto *rightBoxed = uctx_->checker->MaybeBoxType(rightType);
checker::Type *resType;
if (oldRight->IsNumberLiteral() && !oldLeft->IsNumberLiteral() && leftBoxed->IsBuiltinNumeric()) {
resType = leftBoxed;
} else if (oldLeft->IsNumberLiteral() && !oldRight->IsNumberLiteral() && rightBoxed->IsBuiltinNumeric()) {
resType = rightBoxed;
} else {
resType = uctx_->checker->MaybeUnboxType(uctx_->checker->CreateETSUnionType({leftBoxed, rightBoxed}));
}
bexpr->SetLeft(AdjustType(uctx_, oldLeft, resType));
bexpr->SetRight(AdjustType(uctx_, oldRight, resType));
if (bexpr->Result() == oldLeft) {
bexpr->SetResult(bexpr->Left());
} else if (bexpr->Result() == oldRight) {
bexpr->SetResult(bexpr->Right());
}
bexpr->SetTsType(resType);
bexpr->SetOperationType(bexpr->TsType());
}
}
void VisitBinaryExpression(ir::BinaryExpression *bexpr) override
{
if (bexpr->IsArithmetic() || bexpr->IsBitwise() ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_LESS_THAN ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_LESS_THAN_EQUAL ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_GREATER_THAN ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_GREATER_THAN_EQUAL ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_LEFT_SHIFT ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT) {
HandleArithmeticLike(bexpr);
return;
}
if (bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_STRICT_EQUAL ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NOT_STRICT_EQUAL ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_EQUAL ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
HandleEqualityOrInequality(bexpr);
return;
}
if (bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NULLISH_COALESCING) {
bexpr->SetLeft(
AdjustType(uctx_, bexpr->Left(),
uctx_->checker->CreateETSUnionType({bexpr->TsType(), uctx_->checker->GlobalETSNullType(),
uctx_->checker->GlobalETSUndefinedType()})));
bexpr->SetRight(AdjustType(uctx_, bexpr->Right(), bexpr->TsType()));
return;
}
if (bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_LOGICAL_AND ||
bexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_LOGICAL_OR) {
HandleLogical(bexpr);
return;
}
if (bexpr->OperatorType() == lexer::TokenType::KEYW_INSTANCEOF) {
bexpr->SetLeft(AdjustType(uctx_, bexpr->Left(), uctx_->checker->MaybeBoxType(bexpr->Left()->TsType())));
bexpr->SetTsType(uctx_->checker->GlobalETSBooleanType());
return;
}
}
void VisitUnaryExpression(ir::UnaryExpression *uexpr) override
{
if (uexpr->OperatorType() == lexer::TokenType::PUNCTUATOR_TILDE) {
uexpr->SetArgument(AdjustType(uctx_, uexpr->Argument(), uexpr->TsType()));
}
uexpr->SetTsType(uctx_->checker->MaybeUnboxType(uexpr->TsType()));
if (TypeIsBoxedPrimitive(uexpr->Argument()->TsType())) {
uexpr->SetArgument(InsertUnboxing(uctx_, uexpr->Argument()));
}
}
static bool IsStaticMemberExpression(ir::MemberExpression *mexpr)
{
ES2PANDA_ASSERT(mexpr->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS);
auto *propDeclNode = mexpr->Property()->Variable()->Declaration()->Node();
if (propDeclNode->IsMethodDefinition()) {
return propDeclNode->AsMethodDefinition()->IsStatic();
}
if (propDeclNode->IsClassProperty()) {
return propDeclNode->AsClassProperty()->IsStatic();
}
return propDeclNode->IsTSEnumMember();
}
static int GetNumberLiteral(ir::Expression *expr)
{
if (expr->IsNumberLiteral()) {
return static_cast<int>(expr->AsNumberLiteral()->Number().GetDouble());
}
if (expr->IsIdentifier()) {
auto *declNode = expr->Variable()->Declaration()->Node()->Parent()->AsVariableDeclarator();
auto *initVal = declNode->Init();
while (initVal->IsTSAsExpression()) {
initVal = initVal->AsTSAsExpression()->Expr();
}
ES2PANDA_ASSERT(initVal->IsNumberLiteral());
return initVal->AsNumberLiteral()->Number().GetInt();
}
ES2PANDA_UNREACHABLE();
}
checker::Type *GetHandledGetterSetterType(ir::Expression *expr, checker::Type *propType)
{
if (propType->IsETSMethodType()) {
bool needSetter =
expr->Parent()->IsAssignmentExpression() && expr == expr->Parent()->AsAssignmentExpression()->Left();
if (!needSetter) {
if (auto *getterSig = propType->AsETSFunctionType()->FindGetter(); getterSig != nullptr) {
HandleDeclarationNode(uctx_, getterSig->Function());
propType = getterSig->ReturnType();
}
return propType;
}
checker::Signature *setterSig = propType->AsETSFunctionType()->FindSetter();
if (setterSig != nullptr) {
HandleDeclarationNode(uctx_, setterSig->Function());
propType = setterSig->Params()[0]->TsType();
return propType;
}
util::StringView methodName = propType->Variable()->Declaration()->Name();
auto *objType = expr->AsMemberExpression()->Object()->TsType()->AsETSObjectType();
auto *var = objType->GetProperty(methodName, checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
checker::PropertySearchFlags::SEARCH_IN_BASE |
checker::PropertySearchFlags::SEARCH_IN_INTERFACES);
if (var == nullptr) {
return propType;
}
setterSig = var->TsType()->AsETSFunctionType()->FindSetter();
if (setterSig != nullptr) {
HandleDeclarationNode(uctx_, setterSig->Function());
propType = setterSig->Params()[0]->TsType();
return propType;
}
} else if (expr->IsMemberExpression() && expr->AsMemberExpression()->Property()->Variable() != nullptr) {
expr->AsMemberExpression()->Property()->Variable()->SetTsType(propType);
}
return propType;
}
void VisitMemberExpression(ir::MemberExpression *mexpr) override
{
if (mexpr->Kind() == ir::MemberExpressionKind::PROPERTY_ACCESS ||
mexpr->Kind() == ir::MemberExpressionKind::NONE || mexpr->Kind() == ir::MemberExpressionKind::GETTER ||
mexpr->Kind() == ir::MemberExpressionKind::SETTER) {
if (mexpr->Property()->Variable() != nullptr) {
checker::Type *propType = nullptr;
if (mexpr->Property()->Variable()->Declaration() != nullptr &&
mexpr->Property()->Variable()->Declaration()->Node() != nullptr &&
mexpr->Property()->Variable()->Declaration()->Node()->IsTyped() &&
mexpr->Object()->TsType() != nullptr && !mexpr->Object()->TsType()->IsETSAnyType()) {
HandleDeclarationNode(uctx_, mexpr->Property()->Variable()->Declaration()->Node());
propType = mexpr->Property()->Variable()->Declaration()->Node()->AsTyped()->TsType();
} else if (mexpr->Property()->Variable()->TsType() != nullptr) {
propType = mexpr->Property()->Variable()->TsType();
} else {
propType = mexpr->Property()->TsType();
}
ES2PANDA_ASSERT(propType != nullptr);
if (propType->IsETSMethodType()) {
propType = GetHandledGetterSetterType(mexpr, propType);
} else if (mexpr->Property()->Variable() != nullptr) {
mexpr->Property()->Variable()->SetTsType(propType);
}
if (IsRecursivelyUnboxed(propType)) {
mexpr->Property()->SetTsType(propType);
mexpr->SetTsType(propType);
}
} else if (mexpr->Property()->Variable() == nullptr && mexpr->Object()->TsType()->IsETSArrayType() &&
mexpr->Property()->AsIdentifier()->Name() == "length") {
mexpr->SetTsType(uctx_->checker->GlobalIntType());
}
if (mexpr->Object()->TsType() != nullptr && mexpr->Object()->TsType()->IsETSPrimitiveType() &&
!IsStaticMemberExpression(mexpr)) {
mexpr->SetObject(InsertBoxing(uctx_, mexpr->Object()));
}
} else if (mexpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS) {
if (TypeIsBoxedPrimitive(mexpr->Property()->TsType())) {
mexpr->SetProperty(InsertUnboxing(uctx_, mexpr->Property()));
}
if (mexpr->Object()->TsType()->IsETSTupleType()) {
auto tupType = mexpr->Object()->TsType()->AsETSTupleType();
auto index = GetNumberLiteral(mexpr->Property());
ES2PANDA_ASSERT(index >= 0 && (size_t)index < tupType->GetTupleSize());
mexpr->SetTsType(tupType->GetTupleTypesList()[index]);
} else if (mexpr->Object()->TsType()->IsETSArrayType()) {
mexpr->SetTsType(GetArrayElementType(mexpr->Object()->TsType()));
}
} else {
ES2PANDA_UNREACHABLE();
}
}
void VisitTSAsExpression(ir::TSAsExpression *asExpr) override
{
auto *exprType = asExpr->Expr()->TsType();
auto *targetType = asExpr->TypeAnnotation()->TsType();
const auto isBuiltinStringTarget = [this](checker::Type *type) {
return uctx_->checker->Relation()->IsIdenticalTo(type, uctx_->checker->GlobalBuiltinETSStringType());
};
if (targetType->IsETSPrimitiveType() || TypeIsBoxedPrimitive(targetType)) {
if (exprType->IsETSPrimitiveType() || TypeIsBoxedPrimitive(exprType)) {
auto *primTargetType = MaybeRecursivelyUnboxType(uctx_, targetType);
auto *primExprType = MaybeRecursivelyUnboxType(uctx_, exprType);
const bool isBooleanPrimitiveCast =
primTargetType->IsETSPrimitiveType() && primExprType->IsETSPrimitiveType() &&
!primTargetType->IsETSVoidType() && !primExprType->IsETSVoidType() &&
(primTargetType->IsETSBooleanType() != primExprType->IsETSBooleanType());
const bool isNumericCharPrimitiveCast = (primTargetType->HasTypeFlag(checker::TypeFlag::CHAR) &&
primExprType->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC)) ||
(primExprType->HasTypeFlag(checker::TypeFlag::CHAR) &&
primTargetType->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC));
if (isBooleanPrimitiveCast || isNumericCharPrimitiveCast) {
auto *boxedExprType = uctx_->checker->MaybeBoxType(exprType);
auto *boxedTargetType = uctx_->checker->MaybeBoxType(targetType);
asExpr->SetExpr(AdjustType(uctx_, asExpr->Expr(), boxedExprType));
asExpr->TypeAnnotation()->SetTsType(boxedTargetType);
asExpr->SetTsType(boxedTargetType);
} else {
asExpr->TypeAnnotation()->SetTsType(primTargetType);
asExpr->SetExpr(AdjustType(uctx_, asExpr->Expr(), primExprType));
asExpr->SetTsType(primTargetType);
}
} else {
auto *boxedTargetType = uctx_->checker->MaybeBoxType(targetType);
asExpr->TypeAnnotation()->SetTsType(boxedTargetType);
asExpr->SetTsType(boxedTargetType);
}
} else if (exprType->IsETSPrimitiveType()) {
auto *primExprType = MaybeRecursivelyUnboxType(uctx_, exprType);
const bool isCharToStringCast =
primExprType->HasTypeFlag(checker::TypeFlag::CHAR) && isBuiltinStringTarget(targetType);
if (isCharToStringCast) {
auto *boxedExprType = uctx_->checker->MaybeBoxType(exprType);
asExpr->SetExpr(AdjustType(uctx_, asExpr->Expr(), boxedExprType));
} else {
asExpr->SetExpr(AdjustType(uctx_, asExpr->Expr(), targetType));
}
}
asExpr->SetTsType(asExpr->TypeAnnotation()->TsType());
}
void VisitConditionalExpression(ir::ConditionalExpression *cexpr) override
{
if (TypeIsBoxedPrimitive(cexpr->Test()->TsType())) {
cexpr->SetTest(InsertUnboxing(uctx_, cexpr->Test()));
}
auto *tp = cexpr->TsType();
if (!tp->IsETSPrimitiveType() && !TypeIsBoxedPrimitive(tp)) {
cexpr->SetConsequent(AdjustType(uctx_, cexpr->Consequent(), tp));
cexpr->SetAlternate(AdjustType(uctx_, cexpr->Alternate(), tp));
} else {
auto *primTp = uctx_->checker->MaybeUnboxType(tp);
cexpr->SetConsequent(AdjustType(uctx_, cexpr->Consequent(), primTp));
cexpr->SetAlternate(AdjustType(uctx_, cexpr->Alternate(), primTp));
cexpr->SetTsType(primTp);
}
}
void VisitETSNewArrayInstanceExpression(ir::ETSNewArrayInstanceExpression *nexpr) override
{
auto unboxedType = MaybeRecursivelyUnboxType(uctx_, nexpr->TsType());
nexpr->SetTsType(unboxedType);
nexpr->TypeReference()->SetTsType(GetArrayElementType(unboxedType));
nexpr->SetDimension(
AdjustType(uctx_, nexpr->Dimension(), uctx_->checker->MaybeUnboxType(nexpr->Dimension()->TsType())));
}
void VisitETSNewMultiDimArrayInstanceExpression(ir::ETSNewMultiDimArrayInstanceExpression *nexpr) override
{
auto *unboxedType = MaybeRecursivelyUnboxType(uctx_, nexpr->TsType());
nexpr->SetTsType(unboxedType);
auto toUnbox = unboxedType;
for (auto &dim : nexpr->Dimensions()) {
dim = AdjustType(uctx_, dim, uctx_->checker->MaybeUnboxType(dim->TsType()));
toUnbox = GetArrayElementType(toUnbox);
}
nexpr->TypeReference()->SetTsType(toUnbox);
nexpr->SetSignature(
uctx_->checker->CreateBuiltinArraySignature(unboxedType->AsETSArrayType(), nexpr->Dimensions().size()));
}
void VisitBlockExpression(ir::BlockExpression *bexpr) override
{
auto &stmts = bexpr->Statements();
auto *lastStmt = stmts[stmts.size() - 1];
ES2PANDA_ASSERT(lastStmt->IsExpressionStatement());
bexpr->SetTsType(lastStmt->AsExpressionStatement()->GetExpression()->TsType());
}
void VisitSequenceExpression(ir::SequenceExpression *sexpr) override
{
sexpr->SetTsType(sexpr->Sequence().back()->TsType());
}
void HandleLiteral(ir::Literal *lit)
{
if (lit->TsType() == nullptr) {
return;
}
lit->SetTsType(uctx_->checker->MaybeUnboxType(lit->TsType()));
}
void VisitBooleanLiteral(ir::BooleanLiteral *blit) override
{
HandleLiteral(blit);
}
void VisitCharLiteral(ir::CharLiteral *clit) override
{
HandleLiteral(clit);
}
void VisitNumberLiteral(ir::NumberLiteral *nlit) override
{
HandleLiteral(nlit);
}
void HandleVariableRef(ir::Expression *expr)
{
auto *var = expr->Variable();
if (var == nullptr || var->TsType() == nullptr || expr->TsType() == nullptr ||
var->Declaration() == nullptr) {
return;
}
auto *declNode = var->Declaration()->Node();
if (declNode->IsClassProperty()) {
HandleDeclarationNode(uctx_, declNode);
}
if (declNode->IsClassDeclaration() || declNode->IsTSEnumDeclaration() || declNode->IsTSInterfaceDeclaration()) {
return;
}
if (expr->Variable()->TsType()->IsETSPrimitiveType()) {
expr->SetTsType(expr->Variable()->TsType());
} else if (expr->TsType()->IsETSPrimitiveType()) {
expr->SetTsType(uctx_->checker->MaybeBoxType(expr->TsType()));
} else if (expr->Variable()->TsType()->IsETSMethodType()) {
expr->SetTsType(GetHandledGetterSetterType(expr, expr->Variable()->TsType()));
} else {
expr->SetTsType(NormalizeType(uctx_, expr->TsType()));
}
}
void VisitIdentifier(ir::Identifier *id) override
{
HandleVariableRef(id);
}
void VisitTSQualifiedName(ir::TSQualifiedName *qname) override
{
HandleVariableRef(qname);
}
void VisitAssignmentExpression(ir::AssignmentExpression *aexpr) override
{
aexpr->SetRight(AdjustType(uctx_, aexpr->Right(), aexpr->Left()->TsType()));
aexpr->SetTsType(aexpr->Left()->TsType());
}
void VisitClassProperty(ir::ClassProperty *prop) override
{
if (prop->Value() == nullptr) {
return;
}
auto *expectedType = prop->TsType();
if (expectedType == nullptr && prop->Key() != nullptr && prop->Key()->Variable() != nullptr) {
expectedType = prop->Key()->Variable()->TsType();
}
if (expectedType == nullptr) {
return;
}
prop->SetValue(AdjustType(uctx_, prop->Value(), expectedType));
}
void VisitETSParameterExpression(ir::ETSParameterExpression *pexpr) override
{
pexpr->AsETSParameterExpression()->SetInitializer(
AdjustType(uctx_, pexpr->Initializer(), pexpr->Ident()->TsType()));
}
void VisitVariableDeclarator(ir::VariableDeclarator *vdecl) override
{
if (vdecl->Init() != nullptr) {
vdecl->SetInit(AdjustType(uctx_, vdecl->Init(), vdecl->Id()->Variable()->TsType()));
}
}
void VisitTSNonNullExpression(ir::TSNonNullExpression *nnexpr) override
{
if (nnexpr->Expr()->TsType()->IsETSVoidType()) {
return;
}
if (nnexpr->Expr()->TsType()->IsETSPrimitiveType()) {
ReplaceInParent(nnexpr, nnexpr->Expr());
return;
}
nnexpr->SetTsType(uctx_->checker->GetNonNullishType(nnexpr->Expr()->TsType()));
nnexpr->SetOriginalType(nnexpr->TsType());
}
UnboxContext *uctx_;
};
}
static void HandleInstanceMethodsDeclaration(checker::Type *tp, UnboxContext *uctx)
{
for (auto [_, var] : tp->AsETSObjectType()->InstanceMethods()) {
auto *nd = var->Declaration()->Node();
HandleDeclarationNode(uctx, nd);
if (nd->IsMethodDefinition()) {
for (auto overload : nd->AsMethodDefinition()->Overloads()) {
HandleDeclarationNode(uctx, overload);
}
}
}
}
static void HandleStaticMethodDeclaration(checker::Type *tp, UnboxContext *uctx)
{
for (auto [_, var] : tp->AsETSObjectType()->StaticMethods()) {
auto *nd = var->Declaration()->Node();
HandleDeclarationNode(uctx, nd);
if (nd->IsMethodDefinition()) {
for (auto overload : nd->AsMethodDefinition()->Overloads()) {
HandleDeclarationNode(uctx, overload);
}
}
}
}
static void SetUpBuiltinConstructorsAndMethods(UnboxContext *uctx)
{
auto *checker = uctx->checker;
auto setUpType = [uctx](checker::Type *tp) {
if (tp == nullptr || !tp->IsETSObjectType()) {
return;
}
for (auto *sig : tp->AsETSObjectType()->ConstructSignatures()) {
HandleDeclarationNode(uctx, sig->Function());
}
HandleInstanceMethodsDeclaration(tp, uctx);
HandleStaticMethodDeclaration(tp, uctx);
};
for (auto tpix = (size_t)checker::GlobalTypeId::ETS_BOOLEAN; tpix < (size_t)checker::GlobalTypeId::ETS_BIG_INT;
tpix++) {
setUpType(checker->GetGlobalTypesHolder()->GlobalTypes().at(tpix));
}
}
void UnboxPhase::Setup()
{
uctx_.Setup(Context());
SetUpBuiltinConstructorsAndMethods(&uctx_);
}
bool UnboxPhase::PerformForProgram(parser::Program *program)
{
NormalizeAllTypes(&uctx_, program->Ast());
program->Ast()->IterateRecursivelyPostorder([uctx = &uctx_](ir::AstNode *ast) {
if (ast->IsClassProperty() || ast->IsScriptFunction() || ast->IsVariableDeclarator()) {
HandleDeclarationNode(uctx, ast);
} else if (ast->IsForOfStatement()) {
HandleForOfStatement(uctx, ast->AsForOfStatement());
}
});
UnboxVisitor visitor(&uctx_);
program->Ast()->IterateRecursivelyPostorder([&visitor](ir::AstNode *ast) { ast->Accept(&visitor); });
for (auto *stmt : program->Ast()->Statements()) {
RefineSourceRanges(stmt);
}
uctx_.checker->ClearApparentTypes();
return true;
}
}