* 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 "interfaceObjectLiteralLowering.h"
#include "checker/ETSchecker.h"
#include "checker/ets/typeRelationContext.h"
#include "checker/types/typeRelation.h"
#include "compiler/lowering/util.h"
#include "compiler/lowering/scopesInit/scopesInitPhase.h"
namespace ark::es2panda::compiler {
static ir::ETSParameterExpression *AddParam(public_lib::Context *ctx, util::StringView name, ir::TypeNode *type)
{
auto *paramIdent = ctx->AllocNode<ir::Identifier>(name, ctx->Allocator());
if (type != nullptr) {
paramIdent->SetTypeAnnotation(type);
}
return ctx->AllocNode<ir::ETSParameterExpression>(paramIdent, false, ctx->Allocator());
}
using ClassInitializerBuilder = std::function<void(ArenaVector<ir::Statement *> *, ArenaVector<ir::Expression *> *)>;
using ClassBuilder = std::function<void(ArenaVector<ir::AstNode *> &)>;
static std::pair<ir::ScriptFunction *, ir::Identifier *> CreateScriptFunction(public_lib::Context *ctx,
ClassInitializerBuilder const &builder)
{
ArenaVector<ir::Statement *> statements(ctx->Allocator()->Adapter());
ArenaVector<ir::Expression *> params(ctx->Allocator()->Adapter());
ir::ScriptFunction *func;
ir::Identifier *id;
builder(&statements, ¶ms);
auto *body = ctx->AllocNode<ir::BlockStatement>(ctx->Allocator(), std::move(statements));
id = ctx->AllocNode<ir::Identifier>(compiler::Signatures::CTOR, ctx->Allocator());
auto funcSignature = ir::FunctionSignature(nullptr, std::move(params), nullptr);
func = ctx->AllocNode<ir::ScriptFunction>(
ctx->Allocator(), ir::ScriptFunction::ScriptFunctionData {body, std::move(funcSignature),
ir::ScriptFunctionFlags::CONSTRUCTOR |
ir::ScriptFunctionFlags::EXPRESSION,
ir::ModifierFlags::PUBLIC});
ES2PANDA_ASSERT(func != nullptr);
func->SetIdent(id);
return std::make_pair(func, id);
}
static ir::MethodDefinition *CreateClassInstanceInitializer(public_lib::Context *ctx,
const ClassInitializerBuilder &builder)
{
auto [func, id] = CreateScriptFunction(ctx, builder);
auto *funcExpr = ctx->AllocNode<ir::FunctionExpression>(func);
auto *ctor = ctx->AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::CONSTRUCTOR,
id->Clone(ctx->Allocator(), nullptr), funcExpr,
ir::ModifierFlags::NONE, ctx->Allocator(), false);
return ctor;
}
static ir::ClassDeclaration *BuildClass(checker::ETSChecker *checker, util::StringView name,
const ClassBuilder &builder)
{
auto *allocator = checker->ProgramAllocator();
auto *classId = checker->ProgramAllocNode<ir::Identifier>(name, allocator);
auto *classDef =
checker->ProgramAllocNode<ir::ClassDefinition>(allocator, classId, ir::ClassDefinitionModifiers::CLASS_DECL,
ir::ModifierFlags::NONE, Language(Language::Id::ETS));
auto *classDecl = checker->ProgramAllocNode<ir::ClassDeclaration>(classDef, allocator);
auto *const varBinder = checker->VarBinder()->AsETSBinder();
auto *const program = varBinder->Program();
program->Ast()->AddStatement(classDecl);
classDecl->SetParent(program->Ast());
ES2PANDA_ASSERT(varBinder->CheckRecordTablesConsistency(program));
varbinder::BoundContext boundCtx(program->GetRecordTable(), classDef);
ArenaVector<ir::AstNode *> classBody(allocator->Adapter());
builder(classBody);
classDef->AddProperties(std::move(classBody));
compiler::InitScopesPhaseETS::RunExternalNode(classDecl, varBinder);
varBinder->ResolveReference(classDecl);
classDecl->Check(checker);
return classDecl;
}
static constexpr std::string_view OBJECT_LITERAL_SUFFIX = "$ObjectLiteral";
using ReadonlyFieldHolder =
std::tuple<util::UString, util::StringView, checker::Type *>;
using ReadonlyFields = std::vector<ReadonlyFieldHolder>;
using CapturedVariable = std::tuple<varbinder::Variable const *, util::StringView const, ir::Identifier *>;
static std::string_view LoweringName() noexcept
{
return "InterfaceObjectLiteralLowering";
}
std::string_view InterfaceObjectLiteralLowering::Name() const
{
return methodOnly_ ? "InterfaceMethodObjectLiteralLowering" : LoweringName();
}
bool InterfaceObjectLiteralLowering::ShouldLowerObjectLiteral(const ir::ObjectExpression *objectExpr) const
{
return !methodOnly_ || objectExpr->HasMethodDefinition();
}
bool InterfaceObjectLiteralLowering::ShouldLowerObjectLiteral() const
{
return !methodOnly_;
}
static inline bool IsInterfaceType(const checker::Type *type)
{
return type != nullptr && type->IsETSObjectType() &&
type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::INTERFACE);
}
static inline bool IsAbstractClassType(const checker::Type *type)
{
return type != nullptr && type->IsETSObjectType() &&
type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::ABSTRACT);
}
static ir::AstNode *CreateAnonClassImplCtor(public_lib::Context *ctx, ReadonlyFields &readonlyFields)
{
auto *const parser = ctx->parser->AsETSParser();
checker::ETSChecker::ClassInitializerBuilder initBuilder =
[ctx, parser, readonlyFields](ArenaVector<ir::Statement *> *statements, ArenaVector<ir::Expression *> *params) {
for (auto [anonClassFieldName, paramName, retType] : readonlyFields) {
ir::ETSParameterExpression *param =
AddParam(ctx, paramName, ctx->AllocNode<ir::OpaqueTypeNode>(retType, ctx->Allocator()));
params->push_back(param);
auto *paramIdent = ctx->AllocNode<ir::Identifier>(paramName, ctx->Allocator());
statements->push_back(
parser->CreateFormattedStatement("this.@@I1 = @@I2;", anonClassFieldName, paramIdent));
}
AddParam(ctx, varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
};
return CreateClassInstanceInitializer(ctx, initBuilder);
}
static ir::ClassProperty *CreateAnonClassField(public_lib::Context *ctx, ir::MethodDefinition *ifaceMethod,
checker::Type *fieldType, bool isSetter,
util::UString anonClassFieldName)
{
auto *const parser = ctx->parser->AsETSParser();
std::stringstream sourceCode;
sourceCode << "public ";
if (ifaceMethod->Overloads().empty() && !isSetter) {
sourceCode << "readonly ";
}
sourceCode << "@@I1 : @@T2;" << std::endl;
auto field = parser->CreateFormattedClassFieldDefinition(sourceCode.str(), anonClassFieldName, fieldType);
field->SetRange(ifaceMethod->Range());
return field->AsClassProperty();
}
static ir::MethodDefinition *CreateAnonClassFieldGetterSetter(public_lib::Context *ctx,
ir::MethodDefinition *ifaceMethod,
checker::Type *fieldType, bool isSetter,
util::UString anonClassFieldName)
{
auto *const parser = ctx->parser->AsETSParser();
ES2PANDA_ASSERT(ifaceMethod->Function());
ES2PANDA_ASSERT(fieldType != nullptr);
std::stringstream sourceCode;
if (isSetter) {
sourceCode << "public set @@I1 (anonParam:@@T2){" << std::endl;
sourceCode << "this.@@I3 = anonParam" << std::endl;
sourceCode << "}" << std::endl;
ES2PANDA_ASSERT(ifaceMethod->Id());
return parser
->CreateFormattedClassMethodDefinition(sourceCode.str(), ifaceMethod->Id()->Name(), fieldType,
anonClassFieldName)
->AsMethodDefinition();
}
sourceCode << "public get @@I1():@@T2{" << std::endl;
sourceCode << "return this.@@I3" << std::endl;
sourceCode << "}" << std::endl;
return parser
->CreateFormattedClassMethodDefinition(sourceCode.str(), ifaceMethod->Id()->Name(), fieldType,
anonClassFieldName)
->AsMethodDefinition();
}
static void AddAnonClassFieldAndAccessors(public_lib::Context *ctx, ArenaVector<ir::AstNode *> &classBody,
ReadonlyFields &readonlyFields, ir::MethodDefinition *ifaceMethod,
ir::MethodDefinition *copyIfaceMethod)
{
bool isSetter = copyIfaceMethod->Function()->IsSetter();
auto *fieldType = isSetter ? copyIfaceMethod->Function()->Signature()->Params()[0]->TsType()
: copyIfaceMethod->Function()->Signature()->ReturnType();
util::UString anonClassFieldName(ifaceMethod->Id()->Name(), ctx->allocator);
auto *field = CreateAnonClassField(ctx, copyIfaceMethod, fieldType, isSetter, anonClassFieldName);
if (field->IsReadonly()) {
readonlyFields.emplace_back(
std::make_tuple(anonClassFieldName, ifaceMethod->Id()->Name(), field->TypeAnnotation()->TsType()));
}
field->AddModifier(ir::ModifierFlags::GETTER_SETTER);
classBody.emplace_back(field);
SetSourceRangesRecursively(field, ifaceMethod->Range());
auto *accessor = CreateAnonClassFieldGetterSetter(ctx, copyIfaceMethod, fieldType, isSetter, anonClassFieldName);
accessor->SetOriginalNode(field);
classBody.emplace_back(accessor);
SetSourceRangesRecursively(accessor, ifaceMethod->Range());
if (copyIfaceMethod->Overloads().size() == 1) {
auto *anotherAccessor =
CreateAnonClassFieldGetterSetter(ctx, copyIfaceMethod, fieldType, !isSetter, anonClassFieldName);
anotherAccessor->SetOriginalNode(field);
classBody.emplace_back(anotherAccessor);
SetSourceRangesRecursively(anotherAccessor, ifaceMethod->Range());
}
}
static void FillClassBody(public_lib::Context *ctx, ArenaVector<ir::AstNode *> &classBody,
const ArenaVector<ir::AstNode *> &ifaceBody, ReadonlyFields &readonlyFields,
checker::ETSObjectType *currentType = nullptr)
{
for (auto *it : ifaceBody) {
if (it->IsOverloadDeclaration()) {
continue;
}
ES2PANDA_ASSERT(it->IsMethodDefinition());
auto *ifaceMethod = it->AsMethodDefinition();
ES2PANDA_ASSERT(ifaceMethod->Function());
if (!ifaceMethod->Function()->IsGetterOrSetter()) {
continue;
}
bool isSetter = ifaceMethod->Function()->IsSetter();
auto iter = std::find_if(classBody.begin(), classBody.end(), [ifaceMethod](ir::AstNode *ast) -> bool {
if (ast->IsClassProperty()) {
return ast->AsClassProperty()->Id()->Name() == ifaceMethod->Id()->Name();
}
return ast->IsMethodDefinition() && ast->AsMethodDefinition()->Function()->IsGetterOrSetter() &&
ast->AsMethodDefinition()->Id()->Name() == ifaceMethod->Id()->Name();
});
if (iter != classBody.end()) {
continue;
}
auto copyIfaceMethod = ifaceMethod->Clone(ctx->Allocator(), nullptr);
copyIfaceMethod->SetRange(ifaceMethod->Range());
copyIfaceMethod->Function()->SetSignature(ifaceMethod->Function()->Signature());
if (currentType != nullptr) {
auto prop = currentType->GetOwnProperty<checker::PropertyType::INSTANCE_METHOD>(ifaceMethod->Id()->Name());
auto funcType = (prop != nullptr) ? prop->TsType() : nullptr;
if (funcType != nullptr) {
ES2PANDA_ASSERT(funcType->IsETSFunctionType() &&
(funcType->AsETSFunctionType()->FindGetter() != nullptr ||
funcType->AsETSFunctionType()->FindSetter() != nullptr));
auto *sig = isSetter ? funcType->AsETSFunctionType()->FindSetter()
: funcType->AsETSFunctionType()->FindGetter();
copyIfaceMethod->Function()->SetSignature(sig);
}
}
AddAnonClassFieldAndAccessors(ctx, classBody, readonlyFields, ifaceMethod, copyIfaceMethod);
}
}
static void FillAnonClassBody(public_lib::Context *ctx, ArenaVector<ir::AstNode *> &classBody,
ir::TSInterfaceDeclaration *ifaceNode, ReadonlyFields &readonlyFields,
checker::ETSObjectType *interfaceType = nullptr)
{
FillClassBody(ctx, classBody, ifaceNode->Body()->Body(), readonlyFields, interfaceType);
for (auto *extendedIface : ifaceNode->TsType()->AsETSObjectType()->Interfaces()) {
auto *const subInterfaceNode = extendedIface->GetDeclNode()->AsTSInterfaceDeclaration();
subInterfaceNode->Check(ctx->GetChecker()->AsETSChecker());
FillAnonClassBody(ctx, classBody, subInterfaceNode, readonlyFields, extendedIface);
}
}
static std::string GenerateAnonClassName(std::string_view const originalName, bool const addUniqueID = false)
{
auto anonClassName = std::string {originalName};
std::replace(anonClassName.begin(), anonClassName.end(), '.', '$');
anonClassName.append(OBJECT_LITERAL_SUFFIX);
if (addUniqueID) {
anonClassName.append(GenName());
}
return anonClassName;
}
static void AnnotateGeneratedAnonClass(checker::ETSChecker *checker, ir::ClassDefinition *classDef)
{
auto *annoId =
checker->ProgramAllocNode<ir::Identifier>(Signatures::INTERFACE_OBJ_LITERAL, checker->ProgramAllocator());
annoId->SetAnnotationUsage();
auto *annoUsage = checker->ProgramAllocNode<ir::AnnotationUsage>(annoId, checker->ProgramAllocator());
ES2PANDA_ASSERT(annoUsage);
annoUsage->AddModifier(ir::ModifierFlags::ANNOTATION_USAGE);
annoUsage->SetParent(classDef);
annoId->SetParent(annoUsage);
classDef->EmplaceAnnotation(annoUsage);
RefineSourceRanges(annoUsage);
CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, annoUsage);
}
ir::ClassDeclaration *GenerateAnonClass(public_lib::Context *ctx, util::StringView const className,
ir::AstNode const *const decl,
const checker::ETSChecker::ClassBuilder &bodyBuilder, checker::Type *declTsType,
varbinder::Scope *useSiteScope)
{
ES2PANDA_ASSERT(decl != nullptr && (decl->IsTSInterfaceDeclaration() || decl->IsClassDefinition()));
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *allocator = ctx->Allocator();
auto scopeCtx = checker::ScopeContext(checker, useSiteScope);
auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), useSiteScope);
auto *classDecl = BuildClass(checker, className, bodyBuilder);
RefineSourceRanges(classDecl);
auto *classDef = classDecl->Definition();
auto *classType = classDef->TsType()->AsETSObjectType();
if (classType->IsGradual()) {
return classDecl;
}
classDef->SetAnonymousModifier();
auto const range = decl->Range();
classDecl->SetRange(range);
classDef->SetRange(range);
auto const *const typeParams = decl->IsTSInterfaceDeclaration() ? decl->AsTSInterfaceDeclaration()->TypeParams()
: decl->AsClassDefinition()->TypeParams();
if (typeParams != nullptr) {
ArenaVector<checker::Type *> typeArgs(allocator->Adapter());
for (auto const *const param : typeParams->Params()) {
auto const *const var = param->Name()->Variable();
ES2PANDA_ASSERT(var != nullptr && var->TsType()->IsETSTypeParameter());
typeArgs.emplace_back(var->TsType());
}
classType->SetTypeArguments(std::move(typeArgs));
}
if (decl->IsTSInterfaceDeclaration()) {
AnnotateGeneratedAnonClass(checker, classDef);
auto *classImplements =
ctx->AllocNode<ir::TSClassImplements>(ctx->AllocNode<ir::OpaqueTypeNode>((declTsType), allocator));
classImplements->SetParent(classDef);
classDef->EmplaceImplements(classImplements);
classType->RemoveObjectFlag(checker::ETSObjectFlags::RESOLVED_INTERFACES);
checker->GetInterfacesOfClass(classType);
} else {
classType->SetSuperType(declTsType->AsETSObjectType());
}
return classDecl;
}
static checker::Type *GenerateAnonClassFromInterface(public_lib::Context *ctx, ir::TSInterfaceDeclaration *ifaceNode,
ir::AstNode *astNode)
{
if (ifaceNode->GetAnonClass() != nullptr) {
return ifaceNode->GetAnonClass()->Definition()->TsType();
}
auto const classBodyBuilder = [ctx, ifaceNode](ArenaVector<ir::AstNode *> &classBody) -> void {
if (ifaceNode->TsType() == nullptr) {
ifaceNode->Check(ctx->GetChecker()->AsETSChecker());
}
ReadonlyFields readonlyFields {};
FillAnonClassBody(ctx, classBody, ifaceNode, readonlyFields);
classBody.emplace_back(CreateAnonClassImplCtor(ctx, readonlyFields));
};
auto anonClassName = util::UString(GenerateAnonClassName(ifaceNode->InternalName().Utf8()), ctx->Allocator());
auto *classDecl =
GenerateAnonClass(ctx, anonClassName.View(), ifaceNode, classBodyBuilder,
ifaceNode->AsTSInterfaceDeclaration()->TsType(), compiler::NearestScope(astNode));
if (!classDecl->Definition()->TsType()->AsETSObjectType()->IsGradual()) {
ifaceNode->SetAnonClass(classDecl);
}
return classDecl->Definition()->TsType();
}
static void GenerateAnonClassFromAbstractClass(public_lib::Context *ctx, ir::ClassDefinition *abstractClassNode,
ir::AstNode *astNode)
{
if (abstractClassNode->GetAnonClass() != nullptr) {
return;
}
auto classBodyBuilder = [ctx](ArenaVector<ir::AstNode *> &classBody) -> void {
checker::ETSChecker::ClassInitializerBuilder initBuilder =
[ctx]([[maybe_unused]] ArenaVector<ir::Statement *> *statements,
[[maybe_unused]] ArenaVector<ir::Expression *> *params) {
AddParam(ctx, varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
};
auto *ctor = CreateClassInstanceInitializer(ctx, initBuilder);
classBody.emplace_back(ctor);
};
auto anonClassName =
util::UString(GenerateAnonClassName(abstractClassNode->InternalName().Utf8()), ctx->Allocator());
auto *classDecl =
GenerateAnonClass(ctx, anonClassName.View(), abstractClassNode, classBodyBuilder,
abstractClassNode->AsClassDefinition()->TsType(), compiler::NearestScope(astNode));
if (!classDecl->Definition()->TsType()->AsETSObjectType()->IsGradual()) {
abstractClassNode->SetAnonClass(classDecl);
}
}
using InterfaceMethod = std::tuple<util::StringView, checker::Signature *, bool>;
using InterfaceMethods = std::vector<InterfaceMethod>;
static bool AreSignaturesMatching(checker::ETSChecker *checker, checker::TypeRelation *relation,
checker::Signature *existingSig, checker::Signature *newSig)
{
if (relation->SignatureIsSupertypeOf(existingSig, newSig)) {
return true;
}
auto &existingTypeParams = existingSig->TypeParams();
auto &newTypeParams = newSig->TypeParams();
if (existingTypeParams.empty() || newTypeParams.empty()) {
return false;
}
if (!relation->CheckTypeParameterConstraints(existingTypeParams, newTypeParams)) {
return false;
}
auto *substSig = checker->AdjustForTypeParameters(existingSig, newSig);
if (substSig == nullptr) {
return false;
}
checker::SavedTypeRelationFlagsContext savedFlagsCtx(relation, checker::TypeRelationFlag::OVERRIDING_CONTEXT);
return relation->SignatureIsSupertypeOf(substSig, existingSig);
}
static void MethodsHaveBody(checker::TypeRelation *relation, ir::TSInterfaceDeclaration *interfaceDecl,
InterfaceMethods &methods)
{
ES2PANDA_ASSERT(interfaceDecl->Body() != nullptr);
auto const addMethod = [&methods, relation](ir::MethodDefinition const *const methodDef) -> void {
auto *checker = relation->GetChecker()->AsETSChecker();
auto const *const function = methodDef->Function();
auto const &name = function->Id()->Name();
auto *const signature = const_cast<checker::Signature *>(function->Signature());
auto const hasBody = function->HasBody() || (methodDef->Modifiers() & ir::ModifierFlags::DEFAULT) != 0;
auto const hasMatchingMethod = [&name, signature, relation, checker](InterfaceMethod const &item) -> bool {
auto const sameName = std::get<0U>(item) == name;
if (!sameName) {
return false;
}
auto *const existingSignature = std::get<1U>(item);
return AreSignaturesMatching(checker, relation, existingSignature, signature);
};
auto const it = std::find_if(methods.begin(), methods.end(), hasMatchingMethod);
if (it == methods.end()) {
methods.emplace_back(name, signature, hasBody);
} else if (hasBody) {
std::get<2U>(*it) = true;
}
};
for (auto const *const node : interfaceDecl->Body()->Body()) {
if (node->IsOverloadDeclaration()) {
continue;
}
ES2PANDA_ASSERT(node->IsMethodDefinition());
auto methodDef = node->AsMethodDefinition();
ES2PANDA_ASSERT(methodDef->Function());
if (!methodDef->Function()->IsGetterOrSetter()) {
addMethod(methodDef);
}
for (auto const *const overload : methodDef->Overloads()) {
ES2PANDA_ASSERT(overload->Function());
if (!overload->Function()->IsGetterOrSetter()) {
addMethod(overload);
}
}
}
}
static bool CheckInterfaceShouldGenerateAnonClass(checker::ETSChecker *checker,
ir::TSInterfaceDeclaration *interfaceDecl)
{
InterfaceMethods methods {};
auto const checkMethods = [&methods, checker](auto &&self, checker::ETSObjectType const *interfaceType) -> void {
MethodsHaveBody(checker->Relation(), interfaceType->GetDeclNode()->AsTSInterfaceDeclaration(), methods);
for (auto const *type : interfaceType->Interfaces()) {
self(self, type);
}
};
checker::Type const *const iType = interfaceDecl->Check(checker);
if (iType == nullptr || !iType->IsETSObjectType() || iType->AsETSObjectType()->IsGradual()) {
return false;
}
checkMethods(checkMethods, iType->AsETSObjectType());
for (auto const &[_1, _2, hasBody] : methods) {
if (!hasBody) {
return false;
}
}
return true;
}
static ir::AstNode *TransformThisExpression(public_lib::Context *ctx, ir::ThisExpression *thisExpression,
checker::Type *const objectType)
{
auto *const parent = thisExpression->Parent();
auto *const typeNode = ctx->AllocNode<ir::OpaqueTypeNode>(objectType, ctx->Allocator());
auto *const asExpression = ctx->AllocNode<ir::TSAsExpression>(thisExpression, typeNode, false);
asExpression->SetParent(parent);
asExpression->SetRange(thisExpression->Range());
return static_cast<ir::AstNode *>(asExpression);
}
static bool IsVariableLocalToFunction(varbinder::Variable const *variable,
varbinder::FunctionScope const *functionScope)
{
if (functionScope->FindLocal(variable->Name(), varbinder::ResolveBindingOptions::BINDINGS) != nullptr) {
return true;
}
auto const *scope = variable->GetScope();
while (scope != nullptr && scope != functionScope) {
scope = scope->Parent();
}
return scope == functionScope;
}
static ir::AstNode *TransformIdentifier(public_lib::Context *ctx, ir::Identifier *ident,
varbinder::FunctionScope const *const functionScope,
std::vector<CapturedVariable> &capturedVariables)
{
auto const *const variable = ident->Variable();
if (variable->IsLocalVariable() && variable->HasFlag(varbinder::VariableFlags::LOCAL) &&
!variable->Name().StartsWith(compiler::GENSYM_CORE) && !IsVariableLocalToFunction(variable, functionScope)) {
auto *const parent = ident->Parent();
util::StringView newName;
auto const it = std::find_if(capturedVariables.cbegin(), capturedVariables.cend(),
[variable](auto const &item) { return std::get<0U>(item) == variable; });
if (it == capturedVariables.cend()) {
newName = compiler::GenName(ctx->Allocator()).View();
capturedVariables.emplace_back(variable, newName, ident);
} else {
newName = std::get<1U>(*it);
}
auto *const memberExpression = ctx->AllocNode<ir::MemberExpression>(
ctx->AllocNode<ir::ThisExpression>(), ctx->AllocNode<ir::Identifier>(newName, ctx->Allocator()),
ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
memberExpression->SetParent(parent);
memberExpression->SetRange(ident->Range());
return static_cast<ir::AstNode *>(memberExpression);
}
return ident;
}
static void AddCapturedVariables(public_lib::Context *ctx, ArenaVector<ir::AstNode *> &classBody,
ArenaVector<ir::Expression *> &properties,
std::vector<CapturedVariable> &capturedVariables)
{
for (auto const &[variable, fieldName, ident] : capturedVariables) {
constexpr auto const FIELD_DECLARATION = "public @@I1 : @@T2;";
auto *const field = ctx->parser->AsETSParser()->CreateFormattedClassFieldDefinition(
FIELD_DECLARATION, fieldName, variable->TsType());
classBody.emplace_back(field);
auto *const property = ctx->AllocNode<ir::Property>(
ir::PropertyKind::INIT, ctx->AllocNode<ir::Identifier>(fieldName, ctx->Allocator()), ident, false, false);
properties.emplace_back(property);
}
}
static void TransformMethodBody(public_lib::Context *ctx, ir::AstNode *const body, checker::Type *const objectType,
varbinder::FunctionScope const *const functionScope,
std::vector<CapturedVariable> &capturedVariables)
{
ES2PANDA_ASSERT(body != nullptr && functionScope != nullptr);
body->TransformChildrenRecursively(
[=, &capturedVariables](ir::AstNode *node) {
if (node->IsThisExpression()) {
return TransformThisExpression(ctx, node->AsThisExpression(), objectType);
}
if (node->IsIdentifier() && node->Variable() != nullptr &&
node->AsIdentifier()->IsReference(ScriptExtension::ETS)) {
return TransformIdentifier(ctx, node->AsIdentifier(), functionScope, capturedVariables);
}
return node;
},
LoweringName());
}
static void AddMethodsFromLiteral(public_lib::Context *ctx, ArenaVector<ir::AstNode *> &classBody,
ir::ObjectExpression *const objectExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *allocator = ctx->Allocator();
std::vector<CapturedVariable> capturedVariables {};
auto &properties = objectExpr->Properties();
auto it = properties.begin();
while (it != properties.end()) {
ES2PANDA_ASSERT((*it)->IsProperty());
auto *const property = (*it)->AsProperty();
auto *const value = property->Value();
if (!value->IsArrowFunctionExpression() || property->TsType() == nullptr ||
!property->TsType()->IsETSMethodType()) {
++it;
} else {
auto *const key = property->Key();
ES2PANDA_ASSERT(key->IsIdentifier());
ir::ScriptFunction *function = value->AsArrowFunctionExpression()->Function();
varbinder::FunctionScope const *const scope = function->Scope();
function = function->Clone(allocator, nullptr);
function->ClearFlag(ir::ScriptFunctionFlags::ARROW);
function->AddModifier(ir::ModifierFlags::PUBLIC);
auto *const ident = key->AsIdentifier()->Clone(allocator, function);
function->SetIdent(ident);
TransformMethodBody(ctx, function->Body(), objectExpr->TsType(), scope, capturedVariables);
auto *const funcExpr = checker->AllocNode<ir::FunctionExpression>(function);
funcExpr->SetRange(function->Range());
auto *const method = checker->AllocNode<ir::MethodDefinition>(
ir::MethodDefinitionKind::METHOD, ident->Clone(allocator, nullptr), funcExpr,
ir::ModifierFlags::PUBLIC | ir::ModifierFlags::OVERRIDE, allocator, false);
method->SetRange((*it)->Range());
compiler::ClearTypesVariablesAndScopes(method);
classBody.emplace_back(method);
it = properties.erase(it);
}
}
AddCapturedVariables(ctx, classBody, properties, capturedVariables);
}
static checker::Type *GenerateAnonClassFromInterfaceWithMethods(public_lib::Context *ctx,
ir::TSInterfaceDeclaration *const interfaceDecl,
ir::ObjectExpression *const objectExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto const classBodyBuilder = [=](ArenaVector<ir::AstNode *> &classBody) -> void {
if (interfaceDecl->TsType() == nullptr) {
interfaceDecl->Check(checker);
}
ReadonlyFields readonlyFields {};
FillAnonClassBody(ctx, classBody, interfaceDecl, readonlyFields);
AddMethodsFromLiteral(ctx, classBody, objectExpr);
classBody.emplace_back(CreateAnonClassImplCtor(ctx, readonlyFields));
};
auto anonClassName =
util::UString(GenerateAnonClassName(interfaceDecl->InternalName().Utf8(), true), ctx->Allocator());
auto *classDecl = GenerateAnonClass(ctx, anonClassName.View(), interfaceDecl, classBodyBuilder,
objectExpr->AsObjectExpression()->TsType(), compiler::NearestScope(objectExpr));
checker::Type *const classType = classDecl->Definition()->Check(checker);
return classType->IsETSObjectType() && !classType->AsETSObjectType()->IsGradual() ? classType
: checker->GlobalTypeError();
}
static ArenaVector<ark::es2panda::checker::Signature *> GetInterfaceGenericSignature(checker::ETSObjectType *targetType,
util::StringView name)
{
if (targetType != nullptr) {
varbinder::LocalVariable *lv =
targetType->GetProperty(name, checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD |
checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD |
checker::PropertySearchFlags::SEARCH_INSTANCE_DECL |
checker::PropertySearchFlags::SEARCH_IN_INTERFACES);
if (lv != nullptr && lv->TsType() != nullptr && lv->TsType()->IsETSFunctionType()) {
return lv->TsType()->AsETSFunctionType()->CallSignatures();
}
}
return ArenaVector<ark::es2panda::checker::Signature *>();
}
static bool HasMatchingObjectLiteralProperty(checker::TypeRelation *relation, ir::ObjectExpression *objectExpr,
util::StringView name, checker::Signature *signature)
{
for (auto *propExpr : objectExpr->Properties()) {
if (!propExpr->IsProperty()) {
continue;
}
auto *key = propExpr->AsProperty()->Key();
if (!key->IsIdentifier() || key->AsIdentifier()->Name() != name.Utf8()) {
continue;
}
checker::SavedTypeRelationFlagsContext ctx(relation, checker::TypeRelationFlag::OVERRIDING_CONTEXT);
auto *valType = propExpr->AsProperty()->Value()->TsType();
if (valType->IsETSArrowType() &&
relation->SignatureIsSupertypeOf(signature, valType->AsETSFunctionType()->ArrowSignature())) {
return true;
}
}
return false;
}
static void CheckInterface(checker::TypeRelation *relation, ir::TSInterfaceDeclaration *interfaceDecl,
ir::ObjectExpression *objectExpr, InterfaceMethods &methods)
{
auto checkOverriding = [&methods, objectExpr, relation](ir::MethodDefinition const *methodDef,
checker::Signature *sig) {
auto *checker = relation->GetChecker()->AsETSChecker();
auto *func = methodDef->Function();
auto &name = func->Id()->Name();
auto *signature = const_cast<checker::Signature *>(func->Signature());
bool hasBody = func->HasBody() || ((methodDef->Modifiers() & ir::ModifierFlags::DEFAULT) != 0U);
auto it = std::find_if(methods.begin(), methods.end(), [&name, signature, relation, checker](auto &item) {
return std::get<0>(item) == name && AreSignaturesMatching(checker, relation, std::get<1>(item), signature);
});
if (it == methods.end()) {
methods.emplace_back(name, signature, hasBody);
it = std::prev(methods.end());
} else if (hasBody) {
std::get<2>(*it) = true;
}
if (std::get<2>(*it)) {
return;
}
if (HasMatchingObjectLiteralProperty(relation, objectExpr, name, sig)) {
std::get<2>(*it) = true;
}
};
ES2PANDA_ASSERT(interfaceDecl->Body());
for (auto *node : interfaceDecl->Body()->Body()) {
if (node->IsOverloadDeclaration()) {
continue;
}
auto *methodDef = node->AsMethodDefinition();
auto *objType = objectExpr->TsType()->AsETSObjectType();
auto signatures = GetInterfaceGenericSignature(objType, methodDef->Key()->AsIdentifier()->Name());
for (auto *sig : signatures) {
if (!methodDef->Function()->IsGetterOrSetter()) {
checkOverriding(methodDef, sig);
}
}
}
}
static bool CheckInterfaceCanGenerateAnonClass(checker::ETSChecker *checker, ir::TSInterfaceDeclaration *interfaceDecl,
ir::ObjectExpression *objectExpr)
{
InterfaceMethods methods {};
auto const checkMethods = [&methods, objectExpr, checker](auto &&self,
checker::ETSObjectType const *interfaceType) -> void {
CheckInterface(checker->Relation(), interfaceType->GetDeclNode()->AsTSInterfaceDeclaration(), objectExpr,
methods);
for (auto const *type : interfaceType->Interfaces()) {
self(self, type);
}
};
checker::Type const *const iType = interfaceDecl->Check(checker);
if (iType == nullptr || !iType->IsETSObjectType() || iType->AsETSObjectType()->IsGradual()) {
return false;
}
checkMethods(checkMethods, iType->AsETSObjectType());
for (auto const &[_1, _2, hasBody] : methods) {
if (!hasBody) {
return false;
}
}
return true;
}
static checker::Type *ProcessInterfaceWithMethods(public_lib::Context *ctx, ir::TSInterfaceDeclaration *interfaceDecl,
ir::ObjectExpression *objectExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *const helperClass = interfaceDecl->GetAnonClass();
if (objectExpr->HasMethodDefinition()) {
if (helperClass != nullptr || CheckInterfaceCanGenerateAnonClass(checker, interfaceDecl, objectExpr)) {
return GenerateAnonClassFromInterfaceWithMethods(ctx, interfaceDecl, objectExpr);
}
} else {
if (helperClass != nullptr) {
return helperClass->Definition()->TsType();
}
interfaceDecl->Check(checker);
if (CheckInterfaceShouldGenerateAnonClass(checker, interfaceDecl)) {
return GenerateAnonClassFromInterface(ctx, interfaceDecl, objectExpr);
}
}
checker->LogError(diagnostic::INTERFACE_WITH_METHOD, {}, interfaceDecl->Start());
return checker->GlobalTypeError();
}
static checker::Type *GenerateAnonClassFromAbstractClassWithMethods(public_lib::Context *ctx,
ir::ClassDefinition *abstractClassNode,
ir::ObjectExpression *objectExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto classBodyBuilder = [ctx, objectExpr](ArenaVector<ir::AstNode *> &classBody) -> void {
AddMethodsFromLiteral(ctx, classBody, objectExpr);
checker::ETSChecker::ClassInitializerBuilder initBuilder =
[ctx]([[maybe_unused]] ArenaVector<ir::Statement *> *statements,
[[maybe_unused]] ArenaVector<ir::Expression *> *params) {
AddParam(ctx, varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
};
auto *ctor = CreateClassInstanceInitializer(ctx, initBuilder);
classBody.emplace_back(ctor);
};
auto anonClassName =
util::UString(GenerateAnonClassName(abstractClassNode->InternalName().Utf8(), true), ctx->Allocator());
auto *classDecl =
GenerateAnonClass(ctx, anonClassName.View(), abstractClassNode, classBodyBuilder,
abstractClassNode->AsClassDefinition()->TsType(), compiler::NearestScope(objectExpr));
checker::Type *const classType = classDecl->Definition()->Check(checker);
return classType->IsETSObjectType() && !classType->AsETSObjectType()->IsGradual() ? classType
: checker->GlobalTypeError();
}
static checker::Type *ProcessDeclNode(public_lib::Context *ctx, checker::ETSObjectType *targetType,
ir::ObjectExpression *objExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *declNode = targetType->GetDeclNode();
if (declNode->IsTSInterfaceDeclaration()) {
return ProcessInterfaceWithMethods(ctx, declNode->AsTSInterfaceDeclaration(), objExpr);
}
auto *classDef = declNode->AsClassDefinition();
ES2PANDA_ASSERT(classDef->IsAbstract());
if (objExpr->HasMethodDefinition()) {
return GenerateAnonClassFromAbstractClassWithMethods(ctx, classDef, objExpr);
}
if (classDef->GetAnonClass() == nullptr) {
for (auto it : classDef->Body()) {
if (!it->IsMethodDefinition() || !it->AsMethodDefinition()->IsAbstract()) {
continue;
}
ES2PANDA_ASSERT(it->AsMethodDefinition()->Id());
checker->LogError(diagnostic::ABSTRACT_METH_IN_ABSTRACT_CLASS, {it->AsMethodDefinition()->Id()->Name()},
objExpr->Start());
return checker->GlobalTypeError();
}
ES2PANDA_UNREACHABLE();
}
return classDef->GetAnonClass()->Definition()->TsType();
}
static void HandleInterfaceLowering(public_lib::Context *ctx, ir::ObjectExpression *objExpr)
{
auto *checker = ctx->GetChecker()->AsETSChecker();
auto *targetType = objExpr->TsType()->AsETSObjectType();
checker->CheckObjectLiteralKeys(objExpr->Properties());
checker::Type *resultType = ProcessDeclNode(ctx, targetType, objExpr);
if (resultType->IsTypeError()) {
objExpr->SetTsType(resultType);
return;
}
if (!targetType->TypeArguments().empty()) {
ArenaVector<checker::Type *> typeArgTypes(targetType->TypeArguments());
checker::InstantiationContext instantiationCtx(checker, resultType->AsETSObjectType(), std::move(typeArgTypes),
objExpr->Start());
resultType = instantiationCtx.Result();
}
if (const auto *const parent = objExpr->Parent();
parent->IsArrayExpression() && !parent->AsArrayExpression()->TsType()->IsETSTupleType()) {
for (auto *elem : parent->AsArrayExpression()->Elements()) {
if (elem->IsObjectExpression()) {
elem->AsObjectExpression()->SetTsType(resultType);
}
}
}
objExpr->SetTsType(resultType);
}
static bool CheckAbstractClassShouldGenerateAnonClass(ir::ClassDefinition *classDef)
{
auto constructorSigs = classDef->TsType()->AsETSObjectType()->ConstructSignatures();
if (auto res = std::find_if(constructorSigs.cbegin(), constructorSigs.cend(),
[](checker::Signature *sig) -> bool { return sig->MinArgCount() == 0; });
res == constructorSigs.cend()) {
return false;
}
for (auto it : classDef->Body()) {
if (it->IsMethodDefinition() && it->AsMethodDefinition()->IsAbstract()) {
return false;
}
}
return true;
}
static void TransformInterfaceDecl(public_lib::Context *ctx, parser::Program *program,
std::unordered_set<ir::AstNode *> &requiredTypes)
{
auto const cmode = ctx->config->options->GetCompilationMode();
bool isLocal = program == ctx->parserProgram || cmode == CompilationMode::GEN_STD_LIB ||
((cmode == CompilationMode::SIMULTANEOUS || cmode == CompilationMode::SIMULTANEOUS_INCREMENTAL) &&
program->IsBuiltSimultaneously());
auto const isRequired = [&requiredTypes, isLocal](checker::ETSObjectType *type) {
if (isLocal && (type->GetDeclNode()->IsExported() || type->GetDeclNode()->IsDefaultExported())) {
return true;
}
return requiredTypes.find(type->GetDeclNode()) != requiredTypes.end();
};
program->Ast()->IterateRecursivelyPostorder([ctx, program, isRequired](ir::AstNode *ast) -> void {
if (!ast->IsTyped() || ast->AsTyped()->TsType() == nullptr) {
return;
}
if (ast->IsTSInterfaceDeclaration() &&
isRequired(ast->AsTSInterfaceDeclaration()->TsType()->AsETSObjectType()) &&
CheckInterfaceShouldGenerateAnonClass(ctx->GetChecker()->AsETSChecker(), ast->AsTSInterfaceDeclaration())) {
GenerateAnonClassFromInterface(ctx, ast->AsTSInterfaceDeclaration(), ast);
} else if (ast->IsClassDefinition() && ast != program->GlobalClass() &&
ast->AsClassDefinition()->IsAbstract() &&
!ast->AsClassDefinition()->TsType()->AsETSObjectType()->IsGradual() &&
isRequired(ast->AsClassDefinition()->TsType()->AsETSObjectType()) &&
CheckAbstractClassShouldGenerateAnonClass(ast->AsClassDefinition())) {
GenerateAnonClassFromAbstractClass(ctx, ast->AsClassDefinition(), ast);
}
});
}
template <typename F>
static void TraverseObjectLiteralExpressions(parser::Program *program, F const &cb)
{
program->Ast()->IterateRecursivelyPostorder([&cb](ir::AstNode *ast) -> void {
if (!ast->IsObjectExpression()) {
return;
}
auto objExpr = ast->AsObjectExpression();
if ((IsInterfaceType(objExpr->TsType()) || IsAbstractClassType(objExpr->TsType())) &&
!objExpr->TsType()->AsETSObjectType()->IsGradual()) {
cb(ast->AsObjectExpression());
}
});
}
bool InterfaceObjectLiteralLowering::PerformForProgram(parser::Program *prog)
{
auto ctx = Context();
if (prog != ctx->parserProgram) {
return true;
}
auto *varbinder = ctx->GetChecker()->VarBinder()->AsETSBinder();
auto *savedProgram = varbinder->Program();
auto *savedRecordTable = varbinder->GetRecordTable();
auto *savedTopScope = varbinder->TopScope();
if (ShouldLowerObjectLiteral()) {
std::unordered_set<ir::AstNode *> requiredTypes {};
ProgramsToBeEmittedSelector::Apply(ctx, [&requiredTypes](parser::Program *program) {
TraverseObjectLiteralExpressions(program, [&requiredTypes](ir::ObjectExpression *objExpr) {
requiredTypes.insert(objExpr->TsType()->AsETSObjectType()->GetDeclNode());
});
});
ctx->parserProgram->GetExternalDecls()->Visit([ctx, varbinder, &requiredTypes](auto *extProg) {
if (extProg->IsASTLowered()) {
return;
}
varbinder->ResetTopScope(extProg->GlobalScope());
varbinder->SetRecordTable(extProg->GetRecordTable());
varbinder->SetProgram(extProg);
TransformInterfaceDecl(ctx, extProg, requiredTypes);
});
varbinder->ResetTopScope(prog->GlobalScope());
varbinder->SetRecordTable(prog->GetRecordTable());
varbinder->SetProgram(prog);
TransformInterfaceDecl(ctx, prog, requiredTypes);
}
ProgramsToBeEmittedSelector::Apply(ctx, [ctx, varbinder, this](parser::Program *program) {
varbinder->ResetTopScope(program->GlobalScope());
varbinder->SetRecordTable(program->GetRecordTable());
varbinder->SetProgram(program);
TraverseObjectLiteralExpressions(program, [ctx, this](ir::ObjectExpression *expr) {
if (!ShouldLowerObjectLiteral(expr)) {
return;
}
HandleInterfaceLowering(ctx, expr);
});
});
varbinder->ResetTopScope(savedTopScope);
varbinder->SetRecordTable(savedRecordTable);
varbinder->SetProgram(savedProgram);
return true;
}
}