* Copyright (c) 2024-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 "ambientLowering.h"
#include <string_view>
#include "ir/expressions/dummyNode.h"
#include "ir/astNode.h"
#include "compiler/lowering/util.h"
namespace ark::es2panda::compiler {
constexpr size_t MAX_ALLOWED_INDEXERS = 1;
std::string_view AmbientLowering::Name() const
{
static std::string const NAME = "AmbientLowering";
return NAME;
}
bool AmbientLowering::PostconditionForProgram(const parser::Program *program)
{
return !program->Ast()->IsAnyChild(
[](const ir::AstNode *node) -> bool { return node->IsDummyNode() && node->AsDummyNode()->IsDeclareIndexer(); });
}
bool AmbientLowering::PerformForProgram(parser::Program *program)
{
program->Ast()->TransformChildrenRecursively(
[this](ir::AstNode *ast) -> ir::AstNode * {
if (ast->IsClassDefinition()) {
return CreateIndexerMethodIfNeeded(ast->AsClassDefinition());
}
if (ast->IsTSInterfaceBody()) {
return CreateIndexerMethodIfNeeded(ast->AsTSInterfaceBody());
}
return ast;
},
Name());
return true;
}
template <ir::MethodDefinitionKind KIND>
ir::MethodDefinition *CreateMethodFunctionDefinition(ir::DummyNode *node, public_lib::Context *ctx)
{
auto parser = ctx->parser->AsETSParser();
auto allocator = ctx->allocator;
auto indexName = node->GetIndexName();
if (node->IsBrokenStatement()) {
return nullptr;
}
if (indexName == ERROR_LITERAL) {
indexName = "_";
}
std::string sourceCode;
if constexpr (KIND == ir::MethodDefinitionKind::GET) {
sourceCode = "$_get (" + std::string(indexName) + " : @@T1) : @@T2 ";
} else if constexpr (KIND == ir::MethodDefinitionKind::SET) {
sourceCode = "$_set (" + std::string(indexName) + " : @@T1, value : @@T2) : void";
} else {
ES2PANDA_UNREACHABLE();
}
auto methodDefinition =
parser->CreateFormattedClassMethodDefinition(sourceCode, node->IndexTypeAnno()->Clone(allocator, nullptr),
node->GetReturnTypeLiteral()->Clone(allocator, nullptr));
compiler::SetSourceRangesRecursively(methodDefinition, node->Range());
methodDefinition->SetParent(node->Parent());
methodDefinition->AddModifier(ir::ModifierFlags::DECLARE);
ES2PANDA_ASSERT(methodDefinition->AsMethodDefinition()->Function() != nullptr);
methodDefinition->AsMethodDefinition()->Function()->AddModifier(ir::ModifierFlags::DECLARE);
return methodDefinition->AsMethodDefinition();
}
ir::AstNode *AmbientLowering::CreateIndexerMethodIfNeeded(ir::AstNode *ast)
{
if (!ast->IsClassDefinition() && !ast->IsTSInterfaceBody()) {
return ast;
}
const ArenaVector<ir::AstNode *> &classBodyConst =
ast->IsClassDefinition() ? ast->AsClassDefinition()->Body() : ast->AsTSInterfaceBody()->Body();
size_t dummyCount = std::count_if(classBodyConst.cbegin(), classBodyConst.cend(),
[](const ir::AstNode *node) { return node->IsDummyNode(); });
if (dummyCount > MAX_ALLOWED_INDEXERS) {
DE()->LogSemanticError("Only one index signature is allowed in a class or interface.", ast->Start());
RemoveRedundantIndexerDeclarations(ast);
return ast;
}
ES2PANDA_ASSERT(dummyCount <= MAX_ALLOWED_INDEXERS);
if (!std::any_of(classBodyConst.cbegin(), classBodyConst.cend(), [](const ir::AstNode *node) {
return node->IsDummyNode() && node->AsDummyNode()->IsDeclareIndexer();
})) {
return ast;
}
ArenaVector<ir::AstNode *> &classBody =
ast->IsClassDefinition() ? ast->AsClassDefinition()->BodyForUpdate() : ast->AsTSInterfaceBody()->Body();
auto it = classBody.begin();
while (it != classBody.end()) {
if ((*it)->IsDummyNode() && (*it)->AsDummyNode()->IsDeclareIndexer()) {
auto setDefinition =
CreateMethodFunctionDefinition<ir::MethodDefinitionKind::SET>((*it)->AsDummyNode(), Context());
auto getDefinition =
CreateMethodFunctionDefinition<ir::MethodDefinitionKind::GET>((*it)->AsDummyNode(), Context());
classBody.erase(it);
if (setDefinition != nullptr && getDefinition != nullptr) {
classBody.emplace_back(getDefinition);
classBody.emplace_back(setDefinition);
}
break;
}
++it;
}
return ast;
}
void AmbientLowering::RemoveRedundantIndexerDeclarations(ir::AstNode *ast)
{
ArenaVector<ir::AstNode *> &body =
ast->IsClassDefinition() ? ast->AsClassDefinition()->BodyForUpdate() : ast->AsTSInterfaceBody()->Body();
auto dummyStart = std::remove_if(body.begin(), body.end(), [](ir::AstNode *node) {
return node->IsDummyNode() && node->AsDummyNode()->IsDeclareIndexer();
});
body.erase(dummyStart, body.end());
}
}