* Copyright (c) 2025-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 "extensionAccessorLowering.h"
#include "checker/ETSchecker.h"
#include "compiler/lowering/scopesInit/scopesInitPhase.h"
#include "compiler/lowering/util.h"
namespace ark::es2panda::compiler {
static ir::AstNode *CheckAndReturnNode(checker::ETSChecker *checker, ir::AstNode *node);
static bool ResolveAssignmentExpressionToExtensionAccessorCall(ir::AssignmentExpression *assignExpr,
ir::MemberExpression *expr)
{
auto *curParent = assignExpr->Parent()->AsExpressionStatement();
ir::AstNode *callExpr = nullptr;
if (expr->Object()->IsETSNewClassInstanceExpression()) {
callExpr = assignExpr->Left()->AsMemberExpression()->Property()->Parent();
} else {
callExpr = assignExpr->Left()->AsMemberExpression()->Parent();
}
curParent->SetExpression(callExpr->AsExpression());
callExpr->SetParent(curParent);
callExpr->SetRange(curParent->Range());
return true;
}
static bool IsMemberExprExtensionAccessor(ir::AstNode *node)
{
return node->IsMemberExpression() &&
node->AsMemberExpression()->HasMemberKind(ir::MemberExpressionKind::EXTENSION_ACCESSOR);
}
static bool IsAssignExprExtensionSetter(ir::AstNode *node)
{
return node->IsAssignmentExpression() &&
node->AsAssignmentExpression()->OperatorType() == lexer::TokenType::PUNCTUATOR_SUBSTITUTION &&
IsMemberExprExtensionAccessor(node->AsAssignmentExpression()->Left());
}
static void SwitchType(ir::MemberExpression *expr)
{
expr->SetTsType(expr->ExtensionAccessorType());
}
static void TryHandleExtensionAccessor(checker::ETSChecker *checker, ir::MemberExpression *expr)
{
checker::SavedCheckerContextStatus ccStatusHelper(&checker->Context(),
checker::CheckerStatus::IN_EXTENSION_ACCESSOR_CHECK);
SwitchType(expr);
auto oldParent = expr->Parent();
if (IsAssignExprExtensionSetter(oldParent) && expr == oldParent->AsAssignmentExpression()->Left()) {
auto *assignExpr = oldParent->AsAssignmentExpression();
auto *callExpr = checker->CreateExtensionAccessorCall(
checker, expr, ArenaVector<ir::Expression *>(checker->ProgramAllocator()->Adapter()));
auto *rightExpr = assignExpr->AsAssignmentExpression()->Right();
if (IsMemberExprExtensionAccessor(rightExpr)) {
SwitchType(rightExpr->AsMemberExpression());
checker::Type *tsType = rightExpr->AsMemberExpression()->TsType();
checker::ETSFunctionType *eAccType = rightExpr->AsMemberExpression()->ExtensionAccessorType();
auto *copyedRight = rightExpr->Clone(checker->ProgramAllocator(), nullptr);
copyedRight->AsMemberExpression()->SetTsType(tsType);
copyedRight->AsMemberExpression()->SetExtensionAccessorType(eAccType);
copyedRight->AsMemberExpression()->Object()->SetTsType(rightExpr->AsMemberExpression()->Object()->TsType());
rightExpr = checker->CreateExtensionAccessorCall(
checker, copyedRight->AsMemberExpression(),
ArenaVector<ir::Expression *>(checker->ProgramAllocator()->Adapter()));
}
rightExpr->SetParent(callExpr);
callExpr->AsCallExpression()->Arguments().emplace_back(rightExpr);
if (!ResolveAssignmentExpressionToExtensionAccessorCall(assignExpr, expr)) {
return;
};
CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, callExpr);
return;
}
auto *callExpr = checker->CreateExtensionAccessorCall(
checker, expr, ArenaVector<ir::Expression *>(checker->ProgramAllocator()->Adapter()));
callExpr->SetParent(oldParent);
CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, callExpr);
}
static ir::AstNode *CheckAndReturnNode(checker::ETSChecker *checker, ir::AstNode *node)
{
if (!IsAssignExprExtensionSetter(node) && !IsMemberExprExtensionAccessor(node)) {
return node;
}
if (IsAssignExprExtensionSetter(node)) {
node->SetParent(nullptr);
if (node->AsAssignmentExpression()->Left()->AsMemberExpression()->Object()->IsETSNewClassInstanceExpression()) {
return node->AsAssignmentExpression()->Left()->AsMemberExpression()->Property()->Parent();
}
return node->AsAssignmentExpression()->Left()->Parent();
}
auto *oldParent = node->Parent();
TryHandleExtensionAccessor(checker, node->AsMemberExpression());
if (IsAssignExprExtensionSetter(oldParent) && (node == oldParent->AsAssignmentExpression()->Left())) {
return node;
}
if (node->AsMemberExpression()->Object()->IsETSNewClassInstanceExpression()) {
return node->AsMemberExpression()->Property()->Parent();
}
return node->Parent();
}
using AstNodePtr = ir::AstNode *;
bool ExtensionAccessorPhase::PerformForProgram(parser::Program *program)
{
if (program->Extension() != ScriptExtension::ETS) {
return true;
}
checker::ETSChecker *const checker = Context()->GetChecker()->AsETSChecker();
program->Ast()->TransformChildrenRecursively(
[&checker](ir::AstNode *const node) -> AstNodePtr { return CheckAndReturnNode(checker, node); }, Name());
return true;
}
}