* 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.
*/
#ifndef ES2PANDA_PARSER_INCLUDE_PROGRAM_H
#define ES2PANDA_PARSER_INCLUDE_PROGRAM_H
#include <optional>
#include "util/es2pandaMacros.h"
#include "util/ustring.h"
#include "util/path.h"
#include "util/importPathManager.h"
#include "util/enumbitops.h"
#include "ir/statements/blockStatement.h"
#include "lexer/token/sourceLocation.h"
#include "varbinder/varbinder.h"
#include "varbinder/recordTable.h"
#include "libarkbase/mem/pool_manager.h"
#include "libarkbase/os/filesystem.h"
#include <memory>
#include <set>
namespace ark::es2panda::ir {
class BlockStatement;
}
namespace ark::es2panda::varbinder {
class VarBinder;
class FunctionScope;
}
namespace ark::es2panda::compiler {
class CFG;
}
namespace ark::es2panda::checker {
class Checker;
}
namespace ark::es2panda::parser {
#ifndef NDEBUG
constexpr uint32_t POISON_VALUE {0x12346789};
#endif
template <util::ModuleKind KIND>
class ProgramAdapter;
class RecordTableHolder {
public:
void SetRecordTable(varbinder::RecordTable *recordTable)
{
recordTable_ = recordTable;
}
auto *GetRecordTable() const
{
return recordTable_;
}
private:
varbinder::RecordTable *recordTable_ {};
};
template <util::ModuleKind... KINDS>
class ExternalDeclsImpl {
public:
template <util::ModuleKind KIND>
using ProgramsSubmap = ArenaVector<ProgramAdapter<KIND> *>;
using TransitiveExternals = std::tuple<ProgramsSubmap<KINDS>...>;
explicit ExternalDeclsImpl() : transitiveExternals_(ProgramsSubmap<KINDS>()...) {}
template <typename SubmapT>
static constexpr auto GetModuleKindFromSubmapType()
{
using SubmapProgramT = std::remove_pointer_t<typename std::remove_reference_t<SubmapT>::value_type>;
return SubmapProgramT::MODULE_KIND;
}
bool Empty() const
{
bool emptyTransitive = (std::get<ProgramsSubmap<KINDS>>(transitiveExternals_).empty() && ...);
bool emptyDirect = direct_.empty();
return emptyTransitive && emptyDirect;
}
template <util::ModuleKind KIND>
const auto &Get() const
{
return std::get<ProgramsSubmap<KIND>>(transitiveExternals_);
}
template <util::ModuleKind KIND>
auto &Get()
{
return std::get<ProgramsSubmap<KIND>>(transitiveExternals_);
}
template <typename ProgVisitor, util::ModuleKind SUBMAP_KIND>
static constexpr bool INVOCABLE = std::is_invocable_v<ProgVisitor, ProgramAdapter<SUBMAP_KIND> *>;
template <bool SHOULD_UNPACK_PACKAGE = true, bool WITH_METADATA_PROGRAMS = false,
util::ModuleKind... KINDS_TO_VISIT, typename ProgramVisitor>
void Visit(const ProgramVisitor &cb)
{
static_assert(((INVOCABLE<ProgramVisitor, KINDS>) || ...), "Visitor isn't invocable for any kind of programs");
auto submapVisitor = [&cb](const auto &submap) {
constexpr auto CUR_SUBMAP_KIND = GetModuleKindFromSubmapType<decltype(submap)>();
if constexpr (SHOULD_UNPACK_PACKAGE && (CUR_SUBMAP_KIND == util::ModuleKind::PACKAGE) &&
std::is_invocable_v<ProgramVisitor, SourceProgram *>) {
for (auto *pkg : submap) {
pkg->MaybeIteratePackage(cb);
}
return;
}
for (auto *prog : submap) {
ES2PANDA_ASSERT(prog->GetModuleKind() == CUR_SUBMAP_KIND);
if constexpr (INVOCABLE<ProgramVisitor, CUR_SUBMAP_KIND>) {
if constexpr (((sizeof...(KINDS_TO_VISIT) == 1) ||
((KINDS_TO_VISIT != util::ModuleKind::METADATA_DECL) || ...)) &&
(sizeof...(KINDS_TO_VISIT) != 0)) {
cb(prog);
continue;
}
if (prog->IsMetadataLoadedIfApplicable()) {
cb(prog);
}
}
}
};
if constexpr (WITH_METADATA_PROGRAMS && ((KINDS_TO_VISIT != util::ModuleKind::METADATA_DECL) || ...)) {
VisitSubmaps<KINDS_TO_VISIT..., util::ModuleKind::METADATA_DECL>(submapVisitor);
} else {
VisitSubmaps<KINDS_TO_VISIT...>(submapVisitor);
}
}
void Add(Program *progToInsert);
auto &Direct()
{
return direct_;
}
const auto &Direct() const
{
return direct_;
}
private:
template <util::ModuleKind... KINDS_TO_VISIT, typename SubmapVisitor>
void VisitSubmaps(SubmapVisitor &cb)
{
if constexpr (sizeof...(KINDS_TO_VISIT) == 0) {
((cb(std::get<ProgramsSubmap<KINDS>>(transitiveExternals_))), ...);
} else {
((cb(std::get<ProgramsSubmap<KINDS_TO_VISIT>>(transitiveExternals_))), ...);
}
}
private:
TransitiveExternals transitiveExternals_;
using DirectExternalPrograms = ArenaUnorderedMap<ArenaString, Program *>;
DirectExternalPrograms direct_;
friend Program;
};
class Program : public RecordTableHolder {
protected:
Program(const util::ImportInfo &importInfo, ArenaAllocator *allocator, varbinder::VarBinder *varbinder);
friend ArenaAllocator;
public:
using ModuleKind = util::ModuleKind;
using ExternalDecls = ExternalDeclsImpl<ModuleKind::MODULE, ModuleKind::SOURCE_DECL, ModuleKind::PACKAGE,
ModuleKind::ETSCACHE_DECL, ModuleKind::METADATA_DECL>;
using ETSNolintsCollectionMap = ArenaUnorderedMap<const ir::AstNode *, ArenaSet<ETSWarnings>>;
template <util::ModuleKind KIND = util::ModuleKind::MODULE, typename VarBinderT = void>
static ProgramAdapter<KIND> *New(const util::ImportInfo &importInfo, public_lib::Context *context);
virtual ~Program();
NO_COPY_SEMANTIC(Program);
NO_MOVE_SEMANTIC(Program);
ArenaAllocator *Allocator() const
{
return allocator_;
}
void PushVarBinder(varbinder::VarBinder *varbinder);
const varbinder::VarBinder *VarBinder() const;
varbinder::VarBinder *VarBinder();
checker::Checker *Checker();
const checker::Checker *Checker() const;
void PushChecker(checker::Checker *checker);
const util::ImportInfo &GetImportInfo() const
{
return importInfo_;
}
template <typename CB>
void MaybeIteratePackage(const CB &cb);
ScriptExtension Extension() const
{
return extension_;
}
std::string_view SourceCode() const
{
return sourceCode_;
}
const lexer::LineIndex &GetLineIndex() const;
void ResetLineIndexCache() const
{
lineIndex_.reset();
}
util::StringView SourceFilePath() const
{
return sourceFile_.GetPath();
}
const util::Path &SourceFile() const
{
return sourceFile_;
}
util::StringView FileName() const
{
return sourceFile_.GetFileName();
}
util::StringView FileNameWithExtension() const
{
return sourceFile_.GetFileNameWithExtension();
}
util::StringView AbsoluteName() const
{
return sourceFile_.GetAbsolutePath();
}
std::string RelativeFilePath(const public_lib::Context *context) const;
std::optional<std::string> TryRelativeFilePathViaArkTsPaths(const public_lib::Context *context) const;
std::string_view GetCachedRelativeFilePath(const public_lib::Context *context) const;
ir::BlockStatement *Ast()
{
return ast_;
}
const ir::BlockStatement *Ast() const
{
return ast_;
}
void SetAst(ir::BlockStatement *ast)
{
ast_ = ast;
VerifyDeclarationModule();
}
ir::ClassDefinition *GlobalClass();
const ir::ClassDefinition *GlobalClass() const;
void SetGlobalClass(ir::ClassDefinition *globalClass);
ExternalDecls *GetExternalDecls()
{
return &externalDecls_;
}
const ExternalDecls *GetExternalDecls() const
{
return &externalDecls_;
}
void PromoteToMainProgram(public_lib::Context *ctx);
const lexer::SourcePosition &PackageStart() const
{
return packageStartPosition_;
}
void SetPackageStart(const lexer::SourcePosition &start)
{
packageStartPosition_ = start;
}
void SetSource(const ark::es2panda::SourceFile &sourceFile)
{
sourceCode_ = sourceFile.source;
sourceFile_ = util::Path(sourceFile.filePath, Allocator());
moduleInfo_.isDeclForDynamicStaticInterop = sourceFile.isDeclForDynamicStaticInterop;
lineIndex_.reset();
}
void SetPackageInfo(std::string_view mname, util::ModuleKind kind);
const util::ModuleInfo &ModuleInfo() const
{
return moduleInfo_;
}
std::string_view ModuleName() const
{
return moduleInfo_.moduleName;
}
std::string_view ModulePrefix() const
{
return moduleInfo_.modulePrefix;
}
virtual util::ModuleKind GetModuleKind() const
{
return util::ModuleKind::UNKNOWN;
}
template <util::ModuleKind KIND>
bool Is() const
{
return GetModuleKind() == KIND;
}
template <util::ModuleKind KIND>
ProgramAdapter<KIND> *As()
{
ES2PANDA_ASSERT(Is<KIND>());
return static_cast<ProgramAdapter<KIND> *>(this);
}
bool IsDeclForDynamicStaticInterop() const
{
return moduleInfo_.isDeclForDynamicStaticInterop;
}
bool IsDeclarationModule() const
{
return Is<util::ModuleKind::SOURCE_DECL>() || Is<util::ModuleKind::ETSCACHE_DECL>();
}
void SetASTChecked();
void RemoveAstChecked();
bool IsASTChecked();
void MarkASTAsLowered()
{
isASTlowered_ = true;
}
bool IsASTLowered() const
{
return isASTlowered_;
}
bool IsMetadataLoadedIfApplicable() const
{
return !Is<ModuleKind::METADATA_DECL>() || ast_ != nullptr;
}
void SetProgramModified(bool isModified)
{
isModified_ = isModified;
}
bool IsProgramModified() const
{
return isModified_;
}
bool IsStdLib() const
{
return (ModuleName().rfind("std.", 0) == 0) || (ModuleName().rfind("escompat", 0) == 0) ||
(ModuleName().rfind("arkruntime", 0) == 0) || (FileName().Is("etsstdlib"));
}
bool IsBuiltSimultaneously() const;
void SetIsBuiltSimultaneously(bool flag = true)
{
isBuiltSimultaneously_ = flag;
}
varbinder::ClassScope *GlobalClassScope();
const varbinder::ClassScope *GlobalClassScope() const;
varbinder::GlobalScope *GlobalScope();
const varbinder::GlobalScope *GlobalScope() const;
std::string Dump() const;
void DumpSilent() const;
void AddNodeToETSNolintCollection(const ir::AstNode *node, const std::set<ETSWarnings> &warningsCollection);
bool NodeContainsETSNolint(const ir::AstNode *node, ETSWarnings warning);
bool IsDied() const
{
#ifndef NDEBUG
return poisonValue_ != POISON_VALUE;
#else
return false;
#endif
}
compiler::CFG *GetCFG();
const compiler::CFG *GetCFG() const;
ArenaMap<int32_t, varbinder::VarBinder *> &VarBinders()
{
return varbinders_;
}
void Clear();
private:
void VerifyDeclarationModule();
void ResetAst()
{
ast_ = nullptr;
}
void RemoveASTLowered()
{
isASTlowered_ = false;
}
void SetExternalDecls(const ExternalDecls *externalDecls)
{
externalDecls_ = *externalDecls;
}
private:
util::ImportInfo importInfo_;
ArenaAllocator *allocator_ {};
ir::BlockStatement *ast_ {};
util::Path sourceFile_;
std::string_view sourceCode_ {};
mutable std::unique_ptr<lexer::LineIndex> lineIndex_ {};
mutable ArenaString *cachedRelativePath_ {nullptr};
bool isASTlowered_ {};
bool isModified_ {true};
bool isBuiltSimultaneously_ {false};
ScriptExtension extension_ {};
ETSNolintsCollectionMap etsnolintCollection_;
util::ModuleInfo moduleInfo_;
lexer::SourcePosition packageStartPosition_ {};
compiler::CFG *cfg_;
ExternalDecls externalDecls_;
private:
ArenaMap<int32_t, varbinder::VarBinder *> varbinders_;
ArenaVector<checker::Checker *> checkers_;
#ifndef NDEBUG
uint32_t poisonValue_ {POISON_VALUE};
#endif
bool isAstChecked_ {false};
};
class NonPackageProgram : public Program {
public:
using Program::Program;
};
template <util::ModuleKind KIND>
class ProgramAdapter final : public NonPackageProgram {
public:
static constexpr auto MODULE_KIND = KIND;
util::ModuleKind GetModuleKind() const override
{
return MODULE_KIND;
}
using NonPackageProgram::NonPackageProgram;
};
template <>
class ProgramAdapter<util::ModuleKind::PACKAGE> final : public Program {
public:
static constexpr auto MODULE_KIND = util::ModuleKind::PACKAGE;
using Program::Program;
util::ModuleKind GetModuleKind() const override
{
return MODULE_KIND;
}
void AppendFraction(SourceProgram *fraction)
{
fractions_.push_back(fraction);
}
auto &GetUnmergedPackagePrograms()
{
return fractions_;
}
private:
ArenaVector<SourceProgram *> fractions_;
};
template <typename CB>
void Program::MaybeIteratePackage(const CB &cb)
{
auto invokeMaybePassFlag = [&cb](auto *program, bool isPackageFraction) {
constexpr bool SHOULD_INFORM_OF_PACKAGE_FRACTION = std::is_invocable_v<CB, Program *, bool>;
if constexpr (SHOULD_INFORM_OF_PACKAGE_FRACTION) {
cb(program, isPackageFraction);
} else {
cb(program);
}
};
if (Is<util::ModuleKind::PACKAGE>()) {
if (!As<util::ModuleKind::PACKAGE>()->GetUnmergedPackagePrograms().empty()) {
for (auto *fraction : As<util::ModuleKind::PACKAGE>()->GetUnmergedPackagePrograms()) {
ES2PANDA_ASSERT(fraction->Is<util::ModuleKind::MODULE>());
invokeMaybePassFlag(fraction, true);
}
} else {
invokeMaybePassFlag(this, true);
}
} else {
invokeMaybePassFlag(this, false);
}
}
}
#endif