* 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 "lreference.h"
#include "varbinder/declaration.h"
#include "varbinder/variableFlags.h"
#include "compiler/base/destructuring.h"
#include "compiler/core/function.h"
#include "compiler/core/pandagen.h"
#include "compiler/core/ETSGen.h"
#include "checker/types/ets/etsTupleType.h"
#include "ir/base/spreadElement.h"
#include "ir/base/classProperty.h"
#include "ir/base/classDefinition.h"
#include "ir/base/scriptFunction.h"
#include "ir/expressions/assignmentExpression.h"
#include "ir/expressions/memberExpression.h"
#include "ir/statements/variableDeclaration.h"
#include "util/helpers.h"
namespace ark::es2panda::compiler {
LReference::LReferenceBase LReference::CreateBase(CodeGen *cg, const ir::AstNode *node, bool isDeclaration)
{
switch (node->Type()) {
case ir::AstNodeType::IDENTIFIER: {
const util::StringView &name = node->AsIdentifier()->Name();
auto res = cg->Scope()->Find(name, varbinder::ResolveBindingOptions::ALL);
if (res.variable == nullptr) {
res.variable = node->AsIdentifier()->Variable();
}
return {cg, node, ReferenceKind::VAR_OR_GLOBAL, res, isDeclaration};
}
case ir::AstNodeType::MEMBER_EXPRESSION: {
return {cg, node, ReferenceKind::MEMBER, {}, false};
}
case ir::AstNodeType::VARIABLE_DECLARATION: {
ES2PANDA_ASSERT(node->AsVariableDeclaration()->Declarators().size() == 1);
return CreateBase(cg, node->AsVariableDeclaration()->Declarators()[0]->Id(), true);
}
case ir::AstNodeType::VARIABLE_DECLARATOR: {
return CreateBase(cg, node->AsVariableDeclarator()->Id(), true);
}
case ir::AstNodeType::ARRAY_PATTERN:
case ir::AstNodeType::OBJECT_PATTERN: {
return {cg, node, ReferenceKind::DESTRUCTURING, {}, isDeclaration};
}
case ir::AstNodeType::ASSIGNMENT_PATTERN: {
return CreateBase(cg, node->AsAssignmentPattern()->Left(), true);
}
case ir::AstNodeType::REST_ELEMENT: {
return CreateBase(cg, node->AsRestElement()->Argument(), true);
}
case ir::AstNodeType::TS_NON_NULL_EXPRESSION: {
return CreateBase(cg, node->AsTSNonNullExpression()->Expr(), isDeclaration);
}
case ir::AstNodeType::ETS_NEW_CLASS_INSTANCE_EXPRESSION: {
ES2PANDA_ASSERT(node->AsETSNewClassInstanceExpression()->GetArguments().size() == 1);
return CreateBase(cg, node->AsETSNewClassInstanceExpression()->GetArguments()[0], isDeclaration);
}
default: {
ES2PANDA_UNREACHABLE();
}
}
}
JSLReference::JSLReference(CodeGen *cg, const ir::AstNode *node, ReferenceKind refKind,
varbinder::ConstScopeFindResult res, bool isDeclaration)
: LReference(node, refKind, res, isDeclaration), pg_(static_cast<PandaGen *>(cg))
{
if (Kind() != ReferenceKind::MEMBER) {
return;
}
const auto *memberExpr = Node()->AsMemberExpression();
if (memberExpr->Object()->IsSuperExpression()) {
SetKind(ReferenceKind::SUPER);
} else if (memberExpr->IsPrivateReference()) {
SetKind(ReferenceKind::PRIVATE);
privateCtor_ = pg_->AllocReg();
Function::LoadClassContexts(Node(), pg_, privateCtor_, memberExpr->Property()->AsIdentifier()->Name());
}
obj_ = pg_->AllocReg();
memberExpr->Object()->Compile(pg_);
pg_->StoreAccumulator(Node(), obj_);
prop_ = pg_->ToNamedPropertyKey(memberExpr->Property(), memberExpr->IsComputed());
if (std::holds_alternative<util::StringView>(prop_)) {
return;
}
if (std::holds_alternative<int64_t>(prop_) && Kind() != ReferenceKind::SUPER) {
return;
}
memberExpr->Property()->Compile(pg_);
VReg propReg = pg_->AllocReg();
pg_->StoreAccumulator(Node(), propReg);
prop_ = propReg;
}
void JSLReference::GetValue() const
{
switch (Kind()) {
case ReferenceKind::VAR_OR_GLOBAL: {
pg_->LoadVar(Node()->AsIdentifier(), Result());
break;
}
case ReferenceKind::MEMBER: {
if (std::holds_alternative<VReg>(prop_)) {
pg_->LoadObjProperty(Node(), obj_);
break;
}
[[fallthrough]];
}
case ReferenceKind::SUPER: {
pg_->LoadObjProperty(Node(), prop_);
break;
}
case ReferenceKind::PRIVATE: {
pg_->ClassPrivateFieldGet(Node(), privateCtor_, obj_, std::get<util::StringView>(prop_));
break;
}
default: {
ES2PANDA_UNREACHABLE();
}
}
}
void JSLReference::SetValue() const
{
switch (Kind()) {
case ReferenceKind::VAR_OR_GLOBAL: {
pg_->StoreVar(Node(), Result(), IsDeclaration());
break;
}
case ReferenceKind::SUPER: {
pg_->StoreSuperProperty(Node(), obj_, prop_);
break;
}
case ReferenceKind::MEMBER: {
pg_->StoreObjProperty(Node(), obj_, prop_);
break;
}
case ReferenceKind::PRIVATE: {
pg_->ClassPrivateFieldSet(Node(), privateCtor_, obj_, std::get<util::StringView>(prop_));
break;
}
case ReferenceKind::DESTRUCTURING: {
Destructuring::Compile(pg_, Node()->AsExpression());
break;
}
default: {
ES2PANDA_UNREACHABLE();
}
}
}
ETSLReference::ETSLReference(CodeGen *cg, const ir::AstNode *node, ReferenceKind refKind,
varbinder::ConstScopeFindResult res, bool isDeclaration)
: LReference(node, refKind, res, isDeclaration), etsg_(static_cast<ETSGen *>(cg))
{
if (Kind() != ReferenceKind::MEMBER) {
SetKind(ResolveReferenceKind(res.variable));
return;
}
const auto *memberExpr = Node()->AsMemberExpression();
staticObjRef_ = memberExpr->Object()->TsType();
if (!memberExpr->IsComputed() && memberExpr->PropVar() != nullptr &&
etsg_->Checker()->IsVariableStatic(memberExpr->PropVar())) {
return;
}
TargetTypeContext ttctx(etsg_, memberExpr->Object()->TsType());
memberExpr->Object()->Compile(etsg_);
baseReg_ = etsg_->AllocReg();
etsg_->StoreAccumulator(node, baseReg_);
if (memberExpr->IsComputed()) {
TargetTypeContext pttctx(etsg_, memberExpr->Property()->TsType());
memberExpr->Property()->Compile(etsg_);
etsg_->ApplyConversion(memberExpr->Property());
ES2PANDA_ASSERT(memberExpr->Object()->TsType()->IsETSAnyType() ||
etsg_->GetAccumulatorType()->HasTypeFlag(checker::TypeFlag::ETS_INTEGRAL));
propReg_ = etsg_->AllocReg();
etsg_->StoreAccumulator(node, propReg_);
}
}
ETSLReference ETSLReference::Create(CodeGen *const cg, const ir::AstNode *const node, const bool isDeclaration)
{
if (node->Type() == ir::AstNodeType::IDENTIFIER) {
auto *var = node->Variable();
ES2PANDA_ASSERT(var != nullptr);
varbinder::ConstScopeFindResult res;
res.name = var->Name();
res.variable = var;
res.scope = var->GetScope();
auto refKind = ReferenceKind::VAR_OR_GLOBAL;
if (var->HasFlag(varbinder::VariableFlags::PROPERTY)) {
refKind = ReferenceKind::FIELD;
}
return {cg, node, refKind, res, isDeclaration};
}
return std::make_from_tuple<ETSLReference>(CreateBase(cg, node, isDeclaration));
}
ReferenceKind ETSLReference::ResolveReferenceKind(const varbinder::Variable *variable)
{
if (variable->HasFlag(varbinder::VariableFlags::SYNTHETIC)) {
return ReferenceKind::METHOD;
}
if (variable->HasFlag(varbinder::VariableFlags::LOCAL)) {
return ReferenceKind::LOCAL;
}
auto *declNode = variable->Declaration()->Node();
switch (declNode->Type()) {
case ir::AstNodeType::CLASS_PROPERTY: {
auto *classField = declNode->AsClassProperty();
return classField->IsStatic() ? ReferenceKind::STATIC_FIELD : ReferenceKind::FIELD;
}
case ir::AstNodeType::CLASS_DEFINITION: {
auto *classDef = declNode->AsClassDefinition();
return classDef->IsStatic() ? ReferenceKind::STATIC_CLASS : ReferenceKind::CLASS;
}
case ir::AstNodeType::METHOD_DEFINITION: {
return ReferenceKind::METHOD;
}
case ir::AstNodeType::TS_INTERFACE_DECLARATION: {
return ReferenceKind::CLASS;
}
default: {
break;
}
}
return ReferenceKind::LOCAL;
}
void ETSLReference::GetValue() const
{
switch (Kind()) {
case ReferenceKind::MEMBER: {
Node()->AsMemberExpression()->Compile(etsg_);
break;
}
default: {
ES2PANDA_ASSERT(Node()->IsIdentifier());
auto const *const ident = Node()->AsIdentifier();
auto const *const variable = Variable();
etsg_->LoadVar(ident, variable);
if (auto const *const smartType = ident->TsType();
!etsg_->Checker()->Relation()->IsIdenticalTo(smartType, variable->TsType())) {
etsg_->SetAccumulatorType(smartType);
}
break;
}
}
}
void ETSLReference::SetValueComputed(const ir::MemberExpression *memberExpr) const
{
const auto *const objectType = etsg_->Checker()->GetApparentType(memberExpr->Object()->TsType());
if (objectType->IsETSAnyType()) {
if (memberExpr->Property()->TsType()->HasTypeFlag(checker::TypeFlag::ETS_NUMERIC)) {
etsg_->StoreByIndexAny(memberExpr, baseReg_, propReg_);
} else {
etsg_->StoreByValueAny(memberExpr, baseReg_, propReg_);
}
return;
}
if (objectType->IsETSArrayType() || objectType->IsETSResizableArrayType()) {
auto vRegtype = etsg_->GetVRegType(baseReg_);
ES2PANDA_ASSERT(vRegtype != nullptr);
auto *elementType = vRegtype->IsETSArrayType() ? vRegtype->AsETSArrayType()->ElementType()
: vRegtype->AsETSResizableArrayType()->ElementType();
etsg_->StoreArrayElement(Node(), baseReg_, propReg_, elementType);
return;
}
ES2PANDA_ASSERT(objectType->IsETSNeverType());
}
void ETSLReference::SetValueGetterSetter(const ir::MemberExpression *memberExpr) const
{
ES2PANDA_ASSERT(memberExpr->PropVar() != nullptr);
const auto *sig = memberExpr->PropVar()->TsType()->AsETSFunctionType()->FindSetter();
ES2PANDA_ASSERT(sig->Function() != nullptr);
auto argReg = etsg_->AllocReg();
etsg_->StoreAccumulator(Node(), argReg);
if (sig->Function()->IsStatic()) {
etsg_->CallExact(Node(), sig->InternalName(), argReg);
} else if (memberExpr->Object()->IsSuperExpression()) {
etsg_->CallExact(Node(), sig->InternalName(), baseReg_, argReg);
} else {
etsg_->CallVirtual(Node(), sig, baseReg_, argReg);
}
}
void ETSLReference::SetValue() const
{
if (Kind() != ReferenceKind::MEMBER) {
auto ident = Node()->AsIdentifier();
if (ident->Variable()->TsType()->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) {
const auto *sig = ident->Variable()->TsType()->AsETSFunctionType()->FindSetter();
ES2PANDA_ASSERT(sig->Function() != nullptr);
auto argReg = etsg_->AllocReg();
etsg_->StoreAccumulator(Node(), argReg);
etsg_->CallExact(Node(), sig->InternalName(), argReg);
} else {
etsg_->StoreVar(Node()->AsIdentifier(), Result());
}
return;
}
const auto *const memberExpr = Node()->AsMemberExpression();
const auto *const memberExprTsType = memberExpr->TsType();
auto const *objectType = memberExpr->Object()->TsType();
if (!memberExpr->IsIgnoreBox()) {
etsg_->ApplyConversion(Node(), memberExprTsType);
}
if (memberExpr->IsComputed()) {
SetValueComputed(memberExpr);
return;
}
if (objectType->IsETSAnyType()) {
etsg_->StorePropertyByNameAny(memberExpr, baseReg_, memberExpr->Property()->AsIdentifier()->Name());
return;
}
ES2PANDA_ASSERT(memberExpr->PropVar() != nullptr);
if (memberExpr->PropVar()->TsType()->HasTypeFlag(checker::TypeFlag::GETTER_SETTER)) {
SetValueGetterSetter(memberExpr);
return;
}
const util::StringView fullName = etsg_->FormClassPropReference(memberExpr->Property()->Variable());
if (memberExpr->PropVar()->HasFlag(varbinder::VariableFlags::STATIC)) {
etsg_->StoreStaticProperty(Node(), memberExprTsType, fullName);
return;
}
if (objectType->IsETSUnionType()) {
etsg_->StorePropertyByName(Node(), baseReg_,
checker::ETSChecker::FormNamedAccessMetadata(memberExpr->PropVar()));
return;
}
const auto *type = memberExpr->PropVar()->TsType();
etsg_->StoreProperty(Node(), type, baseReg_, fullName);
}
}