* Copyright (c) 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 "checker/ETSchecker.h"
#include "checker/checkerContext.h"
#include "generated/diagnostic.h"
#include "ir/module/importDefaultSpecifier.h"
#include "ir/module/importNamespaceSpecifier.h"
#include "ir/module/importSpecifier.h"
#include "parser/program/program.h"
#include "varbinder/ETSBinder.h"
namespace ark::es2panda::checker {
static std::pair<bool, util::StringView> FindSpecifierForModuleObject(ir::ETSImportDeclaration *importDecl,
varbinder::Variable *binding)
{
ES2PANDA_ASSERT(importDecl != nullptr);
if ((!importDecl->Specifiers().empty()) && importDecl->Specifiers()[0]->IsImportNamespaceSpecifier()) {
return std::make_pair(true, util::StringView());
}
for (auto item : importDecl->Specifiers()) {
if (item->IsImportSpecifier() && (item->AsImportSpecifier()->Imported()->Variable() == binding)) {
util::StringView alias {};
auto specifier = item->AsImportSpecifier();
if (specifier->Imported()->Name() != specifier->Local()->Name()) {
alias = item->AsImportSpecifier()->Local()->Name();
}
return std::make_pair(true, alias);
}
if (item->IsImportDefaultSpecifier() && (item->AsImportDefaultSpecifier()->Local()->Variable() == binding)) {
return std::make_pair(true, item->AsImportDefaultSpecifier()->Local()->Name());
}
}
return std::make_pair(false, util::StringView());
}
void ETSChecker::BuildExportedFunctionSignature(varbinder::Variable *var)
{
auto method = var->AsLocalVariable()->Declaration()->Node()->AsMethodDefinition();
ES2PANDA_ASSERT(method->Parent()->IsClassDefinition() &&
method->Parent()->AsClassDefinition()->Ident()->Name().Is(compiler::Signatures::ETS_GLOBAL));
auto classDef = method->Parent()->AsClassDefinition();
if (classDef->TsType() == nullptr) {
BuildBasicClassProperties(classDef);
}
SavedCheckerContext scc(this, Context().Status(), classDef->TsType()->AsETSObjectType());
auto funcType = BuildMethodSignature(method);
funcType->SetVariable(var);
var->SetTsType(funcType);
method->SetTsType(funcType);
}
static checker::Type *CreateNamespaceMemberType(ETSChecker *checker, util::StringView memberName, checker::Type *type,
varbinder::VariableFlags exportFlags)
{
if (type == nullptr || !type->IsETSFunctionType() || (exportFlags & varbinder::VariableFlags::TYPE_ALIAS) != 0U) {
return type;
}
ArenaVector<checker::Signature *> signatures {checker->ProgramAllocator()->Adapter()};
for (auto *signature : type->AsETSFunctionType()->CallSignaturesOfMethodOrArrow()) {
auto *copy = signature->Copy(checker->ProgramAllocator(), checker->Relation(), checker->GetGlobalTypesHolder());
signatures.push_back(copy);
}
return checker->CreateETSMethodType(memberName, std::move(signatures));
}
static checker::Type *ResolveNamespaceMemberType(ETSChecker *checker, varbinder::Variable *var)
{
if (var == nullptr || var->Declaration() == nullptr || var->Declaration()->Node() == nullptr) {
return checker->GetTypeOfVariable(var);
}
auto *declNode = var->Declaration()->Node();
if (declNode->IsClassDefinition()) {
auto *type = checker->GetTypeFromClassReference(var);
if (type->IsETSObjectType()) {
checker->ETSObjectTypeDeclNode(checker, type->AsETSObjectType());
}
return type;
}
if (declNode->IsTSInterfaceDeclaration()) {
return checker->GetTypeFromInterfaceReference(var);
}
return checker->GetTypeOfVariable(var);
}
static varbinder::VariableFlags NamespaceMemberFlagsForExport(const varbinder::Variable *var)
{
if (var == nullptr) {
return varbinder::VariableFlags::NONE;
}
auto flags = static_cast<varbinder::VariableFlags>(
var->Flags() & (varbinder::VariableFlags::CLASS_OR_INTERFACE_OR_ENUM | varbinder::VariableFlags::TYPE_ALIAS |
varbinder::VariableFlags::NAMESPACE | varbinder::VariableFlags::ANNOTATIONDECL |
varbinder::VariableFlags::ANNOTATIONUSAGE));
if ((flags & (varbinder::VariableFlags::ANNOTATIONDECL | varbinder::VariableFlags::ANNOTATIONUSAGE)) != 0U) {
flags |= varbinder::VariableFlags::TYPE_ALIAS;
}
if (var->Declaration() != nullptr && var->Declaration()->IsFunctionDecl()) {
flags |= varbinder::VariableFlags::METHOD;
}
return flags;
}
static bool ShouldBindSyntheticMemberToSource(const varbinder::Variable *source)
{
return source != nullptr && source->Declaration() != nullptr && source->Declaration()->Node() != nullptr &&
!source->Declaration()->Node()->IsImportSpecifier() &&
!source->Declaration()->Node()->IsImportNamespaceSpecifier() &&
!source->Declaration()->Node()->IsImportDefaultSpecifier();
}
static ir::AstNode *NamespaceMemberDeclNode(checker::ETSObjectType *namespaceObject, const varbinder::Variable *source)
{
return ShouldBindSyntheticMemberToSource(source) ? const_cast<ir::AstNode *>(source->Declaration()->Node())
: namespaceObject->GetDeclNode();
}
struct SyntheticNamespaceMemberArgs {
ETSChecker *checker;
checker::ETSObjectType *namespaceObject;
util::StringView memberName;
checker::Type *type;
varbinder::VariableFlags exportFlags;
const varbinder::Variable *source;
};
static varbinder::LocalVariable *CreateSyntheticNamespaceMember(const SyntheticNamespaceMemberArgs &args)
{
auto *checker = args.checker;
auto *decl = checker->ProgramAllocator()->New<varbinder::LetDecl>(args.memberName);
auto flags = varbinder::VariableFlags::PUBLIC | varbinder::VariableFlags::READONLY |
varbinder::VariableFlags::INITIALIZED | varbinder::VariableFlags::SYNTHETIC | args.exportFlags;
auto *var = checker->ProgramAllocator()->New<varbinder::LocalVariable>(decl, flags);
decl->BindNode(NamespaceMemberDeclNode(args.namespaceObject, args.source));
var->SetTsType(CreateNamespaceMemberType(checker, args.memberName, args.type, args.exportFlags));
return var;
}
static bool CanBindNamespaceMemberToExportedVariable(const varbinder::Variable *var)
{
if (var == nullptr || !var->IsLocalVariable() || var->Declaration() == nullptr ||
var->Declaration()->Node() == nullptr || var->HasFlag(varbinder::VariableFlags::TYPE_ALIAS)) {
return false;
}
switch (var->Declaration()->Node()->Type()) {
case ir::AstNodeType::CLASS_PROPERTY:
case ir::AstNodeType::METHOD_DEFINITION:
case ir::AstNodeType::CLASS_DEFINITION:
return true;
default:
return false;
}
}
template <checker::PropertyType TYPE>
static void AddBindingModuleObjectProperty(ETSChecker *checker, checker::ETSObjectType *moduleObjType,
ir::ETSImportDeclaration *importDecl, varbinder::Variable *var,
const util::StringView &importPath)
{
auto [foundInSpecifiers, aliasedName] = FindSpecifierForModuleObject(importDecl, var);
auto node = var->AsLocalVariable()->Declaration()->Node();
const auto isFromDynamicDefaultImport =
node->IsDefaultExported() && var->HasFlag(varbinder::VariableFlags::DYNAMIC);
const auto isReExportDefault = node->IsDefaultExported() && !importDecl->Specifiers().empty() &&
!importDecl->Specifiers()[0]->IsImportNamespaceSpecifier();
if (!node->IsValidInCurrentPhase() ||
(!(node->IsExported() || isFromDynamicDefaultImport || node->HasExportAlias() || isReExportDefault) ||
!foundInSpecifiers)) {
return;
}
if (node->IsMethodDefinition()) {
checker->BuildExportedFunctionSignature(var);
}
if (!aliasedName.Empty()) {
moduleObjType->AddReExportAlias(var->Declaration()->Name(), aliasedName);
}
auto propNames = checker->FindPropNameForNamespaceImport(var->AsLocalVariable()->Name(), importPath);
for (auto &propName : propNames) {
if (CanBindNamespaceMemberToExportedVariable(var) && propName == var->AsLocalVariable()->Name()) {
moduleObjType->AddProperty<TYPE>(var->AsLocalVariable(), propName);
continue;
}
auto *member =
CreateSyntheticNamespaceMember({checker, moduleObjType, propName, ResolveNamespaceMemberType(checker, var),
NamespaceMemberFlagsForExport(var), var});
moduleObjType->AddProperty<TYPE>(member, propName);
}
}
template <checker::PropertyType TYPE>
void ETSChecker::BindingsModuleObjectAddProperty(checker::ETSObjectType *moduleObjType,
ir::ETSImportDeclaration *importDecl,
const varbinder::Scope::VariableMap &bindings,
const util::StringView &importPath)
{
ES2PANDA_ASSERT(importDecl != nullptr);
for (const auto &binding : bindings) {
AddBindingModuleObjectProperty<TYPE>(this, moduleObjType, importDecl, binding.second, importPath);
}
}
template void ETSChecker::BindingsModuleObjectAddProperty<PropertyType::INSTANCE_DECL>(
ETSObjectType *, ir::ETSImportDeclaration *, const varbinder::Scope::VariableMap &, const util::StringView &);
template void ETSChecker::BindingsModuleObjectAddProperty<PropertyType::INSTANCE_METHOD>(
ETSObjectType *, ir::ETSImportDeclaration *, const varbinder::Scope::VariableMap &, const util::StringView &);
std::vector<util::StringView> ETSChecker::FindPropNameForNamespaceImport(const util::StringView &originalName,
const util::StringView &importPath)
{
std::vector<util::StringView> results;
const auto &store = VarBinder()->AsETSBinder()->GetExportFactsStore();
if (const auto *surface = store.FindSurfaceByResolvedSource(importPath); surface != nullptr) {
for (const auto &fact : VarBinder()->AsETSBinder()->GetExportFacts(surface->program).locals) {
if (fact.localName == originalName && fact.variable != nullptr) {
results.push_back(fact.exportedName);
}
}
}
if (results.empty()) {
results.push_back(originalName);
}
return results;
}
void ETSChecker::SetPropertiesForModuleObject(checker::ETSObjectType *moduleObjType, const util::StringView &importPath,
ir::ETSImportDeclaration *importDecl)
{
parser::Program *program = VarBinder()->AsETSBinder()->GetExternalProgram(importDecl);
ES2PANDA_ASSERT(program != nullptr);
if (!program->IsASTChecked()) {
varbinder::RecordTableContext recordTableCtx(VarBinder()->AsETSBinder(), program);
checker::SavedCheckerContext savedContext(this, Context().Status(), Context().ContainingClass());
if (!VarBinder()->AsETSBinder()->GetGlobalRecordTable()->IsExternal()) {
RemoveStatus(CheckerStatus::IN_EXTERNAL);
}
auto savedProgram = Program();
auto topScopeCtx = varbinder::TopScopeContext(VarBinder(), program->GlobalScope());
VarBinder()->AsETSBinder()->SetProgram(program);
program->SetASTChecked();
program->Ast()->Check(this);
VarBinder()->AsETSBinder()->SetProgram(savedProgram);
}
BindingsModuleObjectAddProperty<checker::PropertyType::STATIC_FIELD>(
moduleObjType, importDecl, program->GlobalClassScope()->StaticFieldScope()->Bindings(), importPath);
BindingsModuleObjectAddProperty<checker::PropertyType::STATIC_METHOD>(
moduleObjType, importDecl, program->GlobalClassScope()->StaticMethodScope()->Bindings(), importPath);
BindingsModuleObjectAddProperty<checker::PropertyType::STATIC_DECL>(
moduleObjType, importDecl, program->GlobalClassScope()->StaticDeclScope()->Bindings(), importPath);
BindingsModuleObjectAddProperty<checker::PropertyType::STATIC_DECL>(
moduleObjType, importDecl, program->GlobalClassScope()->InstanceDeclScope()->Bindings(), importPath);
BindingsModuleObjectAddProperty<checker::PropertyType::STATIC_DECL>(
moduleObjType, importDecl, program->GlobalClassScope()->TypeAliasScope()->Bindings(), importPath);
}
void ETSChecker::SetrModuleObjectTsType(ir::Identifier *local, checker::ETSObjectType *moduleObjType)
{
auto *etsBinder = static_cast<varbinder::ETSBinder *>(VarBinder());
for (auto [bindingName, var] : etsBinder->TopScope()->Bindings()) {
if (bindingName.Is(local->Name().Mutf8())) {
var->SetTsType(moduleObjType);
}
}
}
Type *ETSChecker::GetImportSurfaceObjectType(varbinder::ExportSurfaceId surface, ir::Identifier *ident,
varbinder::LocalVariable *localVar)
{
if (surface.program == nullptr) {
if (localVar != nullptr) {
localVar->SetTsType(GlobalTypeError());
}
return ident->SetTsType(GlobalTypeError());
}
auto const moduleName =
surface.moduleName.Empty() ? util::StringView {surface.program->ModuleName()} : surface.moduleName;
auto const internalName = util::UString(std::string(moduleName)
.append(compiler::Signatures::METHOD_SEPARATOR)
.append(compiler::Signatures::ETS_GLOBAL),
ProgramAllocator())
.View();
ir::AstNode *declNode = ident;
if (localVar != nullptr && localVar->Declaration() != nullptr && localVar->Declaration()->Node() != nullptr &&
localVar->Declaration()->Node()->IsImportNamespaceSpecifier()) {
declNode = localVar->Declaration()->Node()->AsImportNamespaceSpecifier()->Local();
}
auto *moduleObjectType =
ProgramAllocator()->New<ETSObjectType>(ProgramAllocator(), moduleName, internalName,
std::make_tuple(declNode, checker::ETSObjectFlags::CLASS, Relation()));
moduleObjectType->SetSuperType(GlobalETSObjectType());
moduleObjectType->SetExportSurface(surface);
ident->SetTsType(moduleObjectType);
if (localVar != nullptr) {
localVar->SetTsType(moduleObjectType);
} else {
SetrModuleObjectTsType(ident, moduleObjectType);
}
return moduleObjectType;
}
Type *ETSChecker::GetImportNamespaceObjectType(ir::ETSImportDeclaration *importDecl, ir::Identifier *ident)
{
auto surface = exportClosureResolver_->GetImportedSurface(importDecl);
if (surface.program == nullptr) {
LogError(diagnostic::IMPORT_NOT_FOUND_2, {importDecl->ResolvedSource()}, ident->Start());
return ident->SetTsType(GlobalTypeError());
}
auto *var = ident->Variable();
auto *localVar = var != nullptr && var->IsLocalVariable() ? var->AsLocalVariable() : nullptr;
return GetImportSurfaceObjectType(surface, ident, localVar);
}
static checker::PropertyType PropertyTypeForNamespaceExport(const varbinder::Variable *var)
{
if (var == nullptr || var->Declaration() == nullptr) {
return checker::PropertyType::STATIC_DECL;
}
if (var->Declaration()->IsFunctionDecl()) {
return checker::PropertyType::STATIC_METHOD;
}
auto *node = var->Declaration()->Node();
if (node != nullptr && node->IsClassProperty() && node->AsClassProperty()->IsTopLevelLexicalDecl()) {
return checker::PropertyType::STATIC_FIELD;
}
return checker::PropertyType::STATIC_DECL;
}
static bool MaterializeNestedNamespaceMember(ETSChecker *checker, checker::ETSObjectType *namespaceObject,
util::StringView memberName, const checker::ResolvedExportResult *resolved)
{
auto *nestedType = checker->ProgramAllocator()->New<ETSObjectType>(
checker->ProgramAllocator(), resolved->entry.surface.moduleName, resolved->entry.surface.moduleName,
std::make_tuple(namespaceObject->GetDeclNode(), checker::ETSObjectFlags::CLASS, checker->Relation()));
nestedType->SetSuperType(checker->GlobalETSObjectType());
nestedType->SetExportSurface(resolved->entry.surface);
auto *var = CreateSyntheticNamespaceMember(
{checker, namespaceObject, memberName, nestedType, varbinder::VariableFlags::NAMESPACE, nullptr});
namespaceObject->AddProperty<checker::PropertyType::STATIC_DECL>(var, memberName);
return true;
}
static bool MaterializeVariableNamespaceMember(ETSChecker *checker, checker::ETSObjectType *namespaceObject,
util::StringView memberName, varbinder::Variable *var)
{
if (var == nullptr || !var->IsLocalVariable()) {
return false;
}
if (var->AsLocalVariable()->Declaration()->Node()->IsMethodDefinition()) {
checker->BuildExportedFunctionSignature(var);
}
if (var->HasFlag(varbinder::VariableFlags::TYPE_ALIAS)) {
namespaceObject->AddProperty<checker::PropertyType::STATIC_DECL>(var->AsLocalVariable(), memberName);
return true;
}
const auto propertyType = PropertyTypeForNamespaceExport(var);
if (CanBindNamespaceMemberToExportedVariable(var)) {
auto *member = var->AsLocalVariable();
switch (propertyType) {
case checker::PropertyType::STATIC_METHOD:
namespaceObject->AddProperty<checker::PropertyType::STATIC_METHOD>(member, memberName);
break;
case checker::PropertyType::STATIC_FIELD:
namespaceObject->AddProperty<checker::PropertyType::STATIC_FIELD>(member, memberName);
break;
case checker::PropertyType::STATIC_DECL:
namespaceObject->AddProperty<checker::PropertyType::STATIC_DECL>(member, memberName);
break;
default:
ES2PANDA_UNREACHABLE();
}
return true;
}
auto *member =
CreateSyntheticNamespaceMember({checker, namespaceObject, memberName, ResolveNamespaceMemberType(checker, var),
NamespaceMemberFlagsForExport(var), var});
switch (propertyType) {
case checker::PropertyType::STATIC_METHOD:
namespaceObject->AddProperty<checker::PropertyType::STATIC_METHOD>(member, memberName);
break;
case checker::PropertyType::STATIC_FIELD:
namespaceObject->AddProperty<checker::PropertyType::STATIC_FIELD>(member, memberName);
break;
case checker::PropertyType::STATIC_DECL:
namespaceObject->AddProperty<checker::PropertyType::STATIC_DECL>(member, memberName);
break;
default:
ES2PANDA_UNREACHABLE();
}
return true;
}
static void MaterializeErrorNamespaceMember(ETSChecker *checker, checker::ETSObjectType *namespaceObject,
util::StringView memberName)
{
auto *member = CreateSyntheticNamespaceMember(
{checker, namespaceObject, memberName, checker->GlobalTypeError(), varbinder::VariableFlags::NONE, nullptr});
namespaceObject->AddProperty<checker::PropertyType::STATIC_DECL>(member, memberName);
}
bool ETSChecker::MaterializeNamespaceMember(checker::ETSObjectType *namespaceObject, util::StringView memberName,
const lexer::SourcePosition &pos)
{
if (namespaceObject == nullptr || !namespaceObject->HasExportSurface()) {
return false;
}
if (memberName.Is("default")) {
LogError(diagnostic::DEFAULT_EXPORT_DIRECT_IMPORTED, {}, pos);
return true;
}
auto exportSurface = namespaceObject->ExportSurface();
auto *resolver = exportClosureResolver_;
const auto *resolved = resolver->ResolveExportNameWithoutAmbiguousDiagnostic(exportSurface, memberName);
auto namespaceExportMember = checker::ResolvedExportResult {};
if (resolved == nullptr || resolved->status == ExportResolutionStatus::NOT_FOUND) {
namespaceExportMember = resolver->ResolveNamespaceExportMember(exportSurface, memberName);
if (namespaceExportMember.status != ExportResolutionStatus::NOT_FOUND) {
resolved = &namespaceExportMember;
}
}
if (resolved == nullptr) {
return false;
}
auto materialized = checker::ResolvedExportResult {};
if (resolved->status == ExportResolutionStatus::RESOLVED) {
materialized = resolver->SelectMaterializedSurfaceEntry(exportSurface, memberName, *resolved);
resolved = &materialized;
}
switch (resolved->status) {
case ExportResolutionStatus::RESOLVED: {
if (resolved->entry.surface.program != nullptr) {
return MaterializeNestedNamespaceMember(this, namespaceObject, memberName, resolved);
}
return MaterializeVariableNamespaceMember(this, namespaceObject, memberName, resolved->entry.variable);
}
case ExportResolutionStatus::AMBIGUOUS:
LogError(diagnostic::AMBIGUOUS_REFERENCE, {memberName}, pos);
MaterializeErrorNamespaceMember(this, namespaceObject, memberName);
return true;
case ExportResolutionStatus::NOT_FOUND:
return false;
case ExportResolutionStatus::INVALID:
LogError(diagnostic::CYCLIC_EXPORT, pos);
MaterializeErrorNamespaceMember(this, namespaceObject, memberName);
return true;
default:
ES2PANDA_UNREACHABLE();
}
}
}