* 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.
*/
#include "varbinder.h"
#include "public/public.h"
#include "ir/ets/etsDestructuring.h"
namespace ark::es2panda::varbinder {
VarBinder::VarBinder(public_lib::Context *context)
: context_(context), allocator_(context->Allocator()), functionScopes_(allocator_->Adapter())
{
}
void VarBinder::InitTopScope()
{
if (GetContext()->config->options->IsModule()) {
topScope_ = Allocator()->New<ModuleScope>(Allocator());
} else {
topScope_ = Allocator()->New<GlobalScope>(Allocator());
}
scope_ = topScope_;
varScope_ = topScope_;
}
Variable *VarBinder::AddParamDecl(ir::Expression *param, bool throwRedeclaration)
{
ES2PANDA_ASSERT(scope_->IsFunctionParamScope() || scope_->IsCatchParamScope());
auto [var, node] = static_cast<ParamScope *>(scope_)->AddParamDecl(Allocator(), this, param);
ES2PANDA_ASSERT(var != nullptr);
if ((node != nullptr) && throwRedeclaration) {
ThrowRedeclaration(node->Start(), var->Name(), var->Declaration()->Type());
}
return var;
}
void VarBinder::ThrowRedeclaration(const lexer::SourcePosition &pos, const util::StringView &name,
DeclType declType) const
{
ThrowError(pos, diagnostic::VARIABLE_REDECLARED, {name});
switch (declType) {
case DeclType::CLASS:
case DeclType::INTERFACE:
case DeclType::ENUM:
ThrowError(pos, diagnostic::MERGED_DECLS);
break;
default:
break;
}
}
void VarBinder::ThrowLocalRedeclaration(const lexer::SourcePosition &pos, const util::StringView &className) const
{
ThrowError(pos, diagnostic::ID_REDECLARED, {className});
}
void VarBinder::ThrowUnresolvableType(const lexer::SourcePosition &pos, const util::StringView &name) const
{
ThrowError(pos, diagnostic::TYPE_NOT_FOUND, {name});
}
void VarBinder::ThrowTDZ(const lexer::SourcePosition &pos, const util::StringView &name) const
{
ThrowError(pos, diagnostic::TEMPORAL_DEADZONE, {name});
}
void VarBinder::ThrowInvalidCapture(const lexer::SourcePosition &pos, const util::StringView &name) const
{
ThrowError(pos, diagnostic::INVALID_CAPTURE, {name});
}
void VarBinder::ThrowPrivateFieldMismatch(const lexer::SourcePosition &pos, const util::StringView &name) const
{
ThrowError(pos, diagnostic::PRIVATE_FIELD_MISMATCH, {name});
}
void VarBinder::ThrowError(const lexer::SourcePosition &pos, const diagnostic::DiagnosticKind &kind,
const util::DiagnosticMessageParams ¶ms) const
{
context_->diagnosticEngine->ThrowSyntaxError(kind, params, pos);
}
bool VarBinder::IsGlobalIdentifier(const util::StringView &str) const
{
return util::Helpers::IsGlobalIdentifier(str);
}
void VarBinder::IdentifierAnalysis()
{
program_ = context_->parserProgram;
ES2PANDA_ASSERT(program_->Ast());
ES2PANDA_ASSERT(scope_ == topScope_);
ES2PANDA_ASSERT(varScope_ == topScope_);
functionScopes_.push_back(topScope_);
topScope_->BindName(MAIN);
topScope_->BindInternalName(BuildFunctionName(MAIN, 0));
topScope_->CheckDirectEval(context_);
ResolveReferences(program_->Ast());
AddMandatoryParams();
}
void VarBinder::LookupReference(const util::StringView &name)
{
auto res = scope_->Find(name);
if (res.level == 0) {
return;
}
ES2PANDA_ASSERT(res.variable);
res.variable->SetLexical(res.scope);
}
bool VarBinder::InstantiateArgumentsImpl(Scope **scope, Scope *iter, const ir::AstNode *node)
{
if (node->AsScriptFunction()->IsArrow()) {
return false;
}
auto [argumentsVariable, exists] =
(*scope)->AddDecl<ConstDecl, LocalVariable>(Allocator(), FUNCTION_ARGUMENTS, VariableFlags::INITIALIZED);
if (exists && Extension() != ScriptExtension::JS) {
ThrowRedeclaration(node->Start(), FUNCTION_ARGUMENTS, argumentsVariable->Declaration()->Type());
} else if (iter->IsFunctionParamScope()) {
*scope = iter->AsFunctionParamScope()->GetFunctionScope();
(*scope)->InsertBinding(argumentsVariable->Name(), argumentsVariable);
}
(*scope)->AddFlag(ScopeFlags::USE_ARGS);
return true;
}
void VarBinder::InstantiateArguments()
{
auto *iter = scope_;
while (true) {
Scope *scope = iter->IsFunctionParamScope() ? iter : iter->EnclosingVariableScope();
ES2PANDA_ASSERT(scope != nullptr);
const auto *node = scope->Node();
if (scope->IsLoopScope()) {
iter = scope->Parent();
continue;
}
if (!node->IsScriptFunction()) {
break;
}
if (InstantiateArgumentsImpl(&scope, iter, node)) {
break;
}
iter = scope->Parent();
}
}
void VarBinder::PropagateDirectEval() const
{
auto *iter = scope_;
do {
VariableScope *scope = iter->IsFunctionParamScope() ? iter->AsFunctionParamScope()->GetFunctionScope()
: iter->EnclosingVariableScope();
ES2PANDA_ASSERT(scope != nullptr);
scope->AddFlag(ScopeFlags::NO_REG_STORE);
iter = iter->Parent();
} while (iter != nullptr);
}
void VarBinder::InstantiatePrivateContext(const ir::Identifier *ident) const
{
auto *classDef = util::Helpers::GetContainingClassDefinition(ident);
while (classDef != nullptr) {
auto *scope = classDef->Scope();
Variable *variable = scope->FindLocal(classDef->InternalName(), varbinder::ResolveBindingOptions::BINDINGS);
ES2PANDA_ASSERT(variable != nullptr);
if (!variable->HasFlag(VariableFlags::INITIALIZED)) {
break;
}
if (classDef->HasMatchingPrivateKey(ident->Name())) {
variable->SetLexical(scope);
return;
}
classDef = util::Helpers::GetContainingClassDefinition(classDef->Parent());
}
ThrowPrivateFieldMismatch(ident->Start(), ident->Name());
}
void VarBinder::LookupIdentReference(ir::Identifier *ident)
{
if (!ident->IsReference(Extension())) {
return;
}
if (ident->Name().Is(FUNCTION_ARGUMENTS)) {
InstantiateArguments();
}
if (ident->IsPrivateIdent()) {
InstantiatePrivateContext(ident);
return;
}
auto res = scope_->Find(ident->Name(), BindingOptions());
if (res.level != 0) {
ES2PANDA_ASSERT(res.variable);
res.variable->SetLexical(res.scope);
}
if (res.variable == nullptr) {
return;
}
if (res.variable->Declaration()->IsLetOrConstDecl() && !res.variable->HasFlag(VariableFlags::INITIALIZED)) {
ident->SetTdz();
}
ident->SetVariable(res.variable);
}
util::StringView VarBinder::BuildFunctionName(util::StringView name, uint32_t idx)
{
std::stringstream ss;
ss << "func_" << name << "_" << std::to_string(idx);
util::UString internalName(ss.str(), Allocator());
return internalName.View();
}
bool VarBinder::BuildInternalName(ir::ScriptFunction *scriptFunc)
{
auto *funcScope = scriptFunc->Scope();
auto name = util::Helpers::FunctionName(Allocator(), scriptFunc);
uint32_t idx = functionScopes_.size();
funcScope->BindName(name);
funcScope->BindInternalName(BuildFunctionName(name, idx));
return !scriptFunc->IsOverload();
}
void VarBinder::BuildVarDeclaratorIdFromIdentifier(ir::Identifier *ident)
{
const auto &name = ident->Name();
if (IsGlobalIdentifier(name) || name.Is(ERROR_LITERAL)) {
return;
}
auto *variable = scope_->FindLocal(name, varbinder::ResolveBindingOptions::BINDINGS);
ES2PANDA_ASSERT(variable);
ident->SetVariable(variable);
BuildSignatureDeclarationBaseParams(ident->TypeAnnotation());
variable->AddFlag(VariableFlags::INITIALIZED);
}
void VarBinder::BuildVarDeclaratorId(ir::AstNode *childNode)
{
switch (childNode->Type()) {
case ir::AstNodeType::IDENTIFIER: {
BuildVarDeclaratorIdFromIdentifier(childNode->AsIdentifier());
break;
}
case ir::AstNodeType::OBJECT_PATTERN: {
auto *objPattern = childNode->AsObjectPattern();
for (auto *prop : objPattern->Properties()) {
BuildVarDeclaratorId(prop);
}
BuildSignatureDeclarationBaseParams(objPattern->TypeAnnotation());
break;
}
case ir::AstNodeType::ARRAY_PATTERN: {
auto *arrayPattern = childNode->AsArrayPattern();
for (auto *element : childNode->AsArrayPattern()->Elements()) {
BuildVarDeclaratorId(element);
}
BuildSignatureDeclarationBaseParams(arrayPattern->TypeAnnotation());
break;
}
case ir::AstNodeType::ETS_DESTRUCTURING: {
auto dstr = childNode->AsETSDestructuring();
for (auto *element : dstr->Elements()) {
BuildVarDeclaratorId(element);
}
break;
}
case ir::AstNodeType::ASSIGNMENT_PATTERN: {
ResolveReference(childNode->AsAssignmentPattern()->Right());
BuildVarDeclaratorId(childNode->AsAssignmentPattern()->Left());
break;
}
case ir::AstNodeType::PROPERTY: {
ResolveReference(childNode->AsProperty()->Key());
BuildVarDeclaratorId(childNode->AsProperty()->Value());
break;
}
case ir::AstNodeType::REST_ELEMENT: {
BuildVarDeclaratorId(childNode->AsRestElement()->Argument());
break;
}
default:
break;
}
}
void VarBinder::BuildVarDeclarator(ir::VariableDeclarator *varDecl)
{
if (varDecl->Parent()->AsVariableDeclaration()->Kind() == ir::VariableDeclaration::VariableDeclarationKind::VAR) {
ResolveReferences(varDecl);
return;
}
if (varDecl->Init() != nullptr) {
ResolveReference(varDecl->Init());
}
BuildVarDeclaratorId(varDecl->Id());
}
void VarBinder::BuildClassProperty(const ir::ClassProperty *prop)
{
const ir::ScriptFunction *ctor = util::Helpers::GetContainingConstructor(prop);
ES2PANDA_ASSERT(ctor != nullptr);
auto scopeCtx = LexicalScope<FunctionScope>::Enter(this, ctor->Scope());
ResolveReferences(prop);
}
void VarBinder::InitializeClassBinding(ir::ClassDefinition *classDef)
{
auto res = scope_->Find(classDef->Ident()->Name());
ES2PANDA_ASSERT(res.variable && res.variable->Declaration()->IsLetDecl());
res.variable->AddFlag(VariableFlags::INITIALIZED);
}
void VarBinder::InitializeClassIdent(ir::ClassDefinition *classDef)
{
auto res = scope_->Find(classDef->Ident()->Name());
ES2PANDA_ASSERT(res.variable &&
(res.variable->Declaration()->IsConstDecl() || res.variable->Declaration()->IsReadonlyDecl()));
res.variable->AddFlag(VariableFlags::INITIALIZED);
}
void VarBinder::BuildClassDefinition(ir::ClassDefinition *classDef)
{
if (classDef->Parent()->IsClassDeclaration() || classDef->Parent()->IsETSStructDeclaration()) {
InitializeClassBinding(classDef);
}
auto scopeCtx = LexicalScope<LocalScope>::Enter(this, classDef->Scope());
if (classDef->Super() != nullptr) {
ResolveReference(classDef->Super());
}
Variable *variable = scope_->FindLocal(classDef->InternalName(), varbinder::ResolveBindingOptions::BINDINGS);
ES2PANDA_ASSERT(variable != nullptr);
variable->AddFlag(VariableFlags::INITIALIZED);
if (classDef->Ident() != nullptr) {
InitializeClassIdent(classDef);
}
ResolveReference(classDef->Ctor());
for (auto *stmt : classDef->Body()) {
ResolveReference(stmt);
}
}
void VarBinder::BuildForUpdateLoop(ir::ForUpdateStatement *forUpdateStmt)
{
auto *loopScope = forUpdateStmt->Scope();
auto declScopeCtx = LexicalScope<LoopDeclarationScope>::Enter(this, loopScope->DeclScope());
if (forUpdateStmt->Init() != nullptr) {
ResolveReference(forUpdateStmt->Init());
}
if (forUpdateStmt->Update() != nullptr) {
ResolveReference(forUpdateStmt->Update());
}
auto loopCtx = LexicalScope<LoopScope>::Enter(this, loopScope);
if (forUpdateStmt->Test() != nullptr) {
ResolveReference(forUpdateStmt->Test());
}
ResolveReference(forUpdateStmt->Body());
loopCtx.GetScope()->ConvertToVariableScope(Allocator());
}
void VarBinder::BuildForInOfLoop(varbinder::LoopScope *loopScope, ir::AstNode *left, ir::Expression *right,
ir::Statement *body)
{
auto declScopeCtx = LexicalScope<LoopDeclarationScope>::Enter(this, loopScope->DeclScope());
ResolveReference(right);
ResolveReference(left);
auto loopCtx = LexicalScope<LoopScope>::Enter(this, loopScope);
ResolveReference(body);
loopCtx.GetScope()->ConvertToVariableScope(Allocator());
}
void VarBinder::BuildCatchClause(ir::CatchClause *catchClauseStmt)
{
if (catchClauseStmt->Param() != nullptr) {
auto paramScopeCtx = LexicalScope<CatchParamScope>::Enter(this, catchClauseStmt->Scope()->ParamScope());
ResolveReference(catchClauseStmt->Param());
}
auto scopeCtx = LexicalScope<CatchScope>::Enter(this, catchClauseStmt->Scope());
ResolveReference(catchClauseStmt->Body());
}
void VarBinder::BuildTypeAliasDeclaration(ir::TSTypeAliasDeclaration *const typeAliasDecl)
{
if (typeAliasDecl->TypeParams() != nullptr) {
const auto typeAliasScope = LexicalScope<LocalScope>::Enter(this, typeAliasDecl->TypeParams()->Scope());
ResolveReferences(typeAliasDecl);
return;
}
ResolveReferences(typeAliasDecl);
}
void VarBinder::AddCompilableFunction(ir::ScriptFunction *func)
{
if (func->IsArrow()) {
VariableScope *outerVarScope = scope_->EnclosingVariableScope();
ES2PANDA_ASSERT(outerVarScope != nullptr);
outerVarScope->AddFlag(ScopeFlags::INNER_ARROW);
}
AddCompilableFunctionScope(func->Scope());
}
void VarBinder::AddCompilableFunctionScope(varbinder::FunctionScope *funcScope)
{
functionScopes_.push_back(funcScope);
}
void VarBinder::VisitScriptFunction(ir::ScriptFunction *func)
{
auto *funcScope = func->Scope();
{
if (funcScope == nullptr) {
ES2PANDA_ASSERT(GetContext()->diagnosticEngine->IsAnyError());
return;
}
auto paramScopeCtx = LexicalScope<FunctionParamScope>::Enter(this, funcScope->ParamScope());
for (auto *param : func->Params()) {
ResolveReference(param);
}
}
if (func->ReturnTypeAnnotation() != nullptr) {
ResolveReference(func->ReturnTypeAnnotation());
}
if (!BuildInternalName(func)) {
if (func->Body() == nullptr) {
return;
}
auto stmt = func->Body()->AsBlockStatement()->Statements();
auto scopeCtx = LexicalScope<FunctionScope>::Enter(this, funcScope);
std::function<void(ir::AstNode *)> doNode = [&](ir::AstNode *node) {
if (node->IsTSInterfaceDeclaration() || node->IsTSEnumDeclaration()) {
ResolveReference(node);
}
node->Iterate([&](ir::AstNode *child) { doNode(child); });
};
doNode(func->Body());
return;
}
if (!func->IsDeclare()) {
AddCompilableFunction(func);
}
auto scopeCtx = LexicalScope<FunctionScope>::Enter(this, funcScope);
if (func->Body() != nullptr) {
ResolveReference(func->Body());
}
}
void VarBinder::VisitScriptFunctionWithPotentialTypeParams(ir::ScriptFunction *func)
{
if (func->TypeParams() != nullptr) {
auto typeParamScopeCtx = LexicalScope<Scope>::Enter(this, func->TypeParams()->Scope());
VisitScriptFunction(func);
return;
}
VisitScriptFunction(func);
}
void VarBinder::ResolveReferenceDoWhileHelper(ir::AstNode *childNode)
{
auto *doWhileStatement = childNode->AsDoWhileStatement();
{
auto loopScopeCtx = LexicalScope<LoopScope>::Enter(this, doWhileStatement->Scope());
ResolveReference(doWhileStatement->Body());
}
return ResolveReference(doWhileStatement->Test());
}
void VarBinder::ResolveReferenceWhileHelper(ir::AstNode *childNode)
{
auto *whileStatement = childNode->AsWhileStatement();
ResolveReference(whileStatement->Test());
auto loopScopeCtx = LexicalScope<LoopScope>::Enter(this, whileStatement->Scope());
return ResolveReference(whileStatement->Body());
}
void VarBinder::ResolveReference(ir::AstNode *childNode)
{
switch (childNode->Type()) {
case ir::AstNodeType::IDENTIFIER:
LookupIdentReference(childNode->AsIdentifier());
return ResolveReferences(childNode);
case ir::AstNodeType::SUPER_EXPRESSION:
ES2PANDA_ASSERT(scope_->EnclosingVariableScope() != nullptr);
scope_->EnclosingVariableScope()->AddFlag(ScopeFlags::USE_SUPER);
return ResolveReferences(childNode);
case ir::AstNodeType::SCRIPT_FUNCTION: {
return VisitScriptFunctionWithPotentialTypeParams(childNode->AsScriptFunction());
}
case ir::AstNodeType::VARIABLE_DECLARATOR:
return BuildVarDeclarator(childNode->AsVariableDeclarator());
case ir::AstNodeType::CLASS_DEFINITION:
return BuildClassDefinition(childNode->AsClassDefinition());
case ir::AstNodeType::CLASS_PROPERTY:
return BuildClassProperty(childNode->AsClassProperty());
case ir::AstNodeType::BLOCK_STATEMENT: {
auto scopeCtx = LexicalScope<Scope>::Enter(this, childNode->AsBlockStatement()->Scope());
return ResolveReferences(childNode);
}
case ir::AstNodeType::BLOCK_EXPRESSION: {
auto scopeCtx = LexicalScope<Scope>::Enter(this, childNode->AsBlockExpression()->Scope());
return ResolveReferences(childNode);
}
case ir::AstNodeType::SWITCH_STATEMENT: {
auto scopeCtx = LexicalScope<LocalScope>::Enter(this, childNode->AsSwitchStatement()->Scope());
return ResolveReferences(childNode);
}
case ir::AstNodeType::DO_WHILE_STATEMENT:
return ResolveReferenceDoWhileHelper(childNode);
case ir::AstNodeType::WHILE_STATEMENT:
return ResolveReferenceWhileHelper(childNode);
case ir::AstNodeType::FOR_UPDATE_STATEMENT:
return BuildForUpdateLoop(childNode->AsForUpdateStatement());
case ir::AstNodeType::FOR_IN_STATEMENT: {
auto *forInStmt = childNode->AsForInStatement();
return BuildForInOfLoop(forInStmt->Scope(), forInStmt->Left(), forInStmt->Right(), forInStmt->Body());
}
case ir::AstNodeType::FOR_OF_STATEMENT: {
auto *forOfStmt = childNode->AsForOfStatement();
return BuildForInOfLoop(forOfStmt->Scope(), forOfStmt->Left(), forOfStmt->Right(), forOfStmt->Body());
}
case ir::AstNodeType::CATCH_CLAUSE:
return BuildCatchClause(childNode->AsCatchClause());
case ir::AstNodeType::TS_TYPE_ALIAS_DECLARATION:
return BuildTypeAliasDeclaration(childNode->AsTSTypeAliasDeclaration());
default:
return HandleCustomNodes(childNode);
}
}
void VarBinder::ResolveReferences(const ir::AstNode *parent)
{
parent->Iterate([this](auto *childNode) { ResolveReference(childNode); });
}
LocalVariable *VarBinder::AddMandatoryParam(const std::string_view &name)
{
ES2PANDA_ASSERT(scope_->IsFunctionParamScope());
auto *decl = Allocator()->New<ParameterDecl>(name);
auto *param = Allocator()->New<LocalVariable>(decl, VariableFlags::VAR);
auto &funcParams = scope_->AsFunctionParamScope()->Params();
funcParams.insert(funcParams.begin(), param);
scope_->AsFunctionParamScope()->GetFunctionScope()->InsertBinding(decl->Name(), param);
scope_->InsertBinding(decl->Name(), param);
return param;
}
void VarBinder::LookUpMandatoryReferences(const FunctionScope *funcScope, bool needLexicalFuncObj)
{
LookupReference(MANDATORY_PARAM_NEW_TARGET);
LookupReference(MANDATORY_PARAM_THIS);
if (funcScope->HasFlag(ScopeFlags::USE_ARGS)) {
LookupReference(FUNCTION_ARGUMENTS);
}
if (needLexicalFuncObj) {
LookupReference(MANDATORY_PARAM_FUNC);
}
}
void VarBinder::AddMandatoryParams()
{
ES2PANDA_ASSERT(scope_ == topScope_);
ES2PANDA_ASSERT(!functionScopes_.empty());
auto iter = functionScopes_.begin();
[[maybe_unused]] auto *funcScope = *iter++;
ES2PANDA_ASSERT(funcScope->IsGlobalScope() || funcScope->IsModuleScope());
const auto *options = context_->config->options;
if (options->IsDirectEval()) {
AddMandatoryParams(EVAL_SCRIPT_MANDATORY_PARAMS);
topScope_->ParamScope()->Params().back()->SetLexical(topScope_);
} else {
AddMandatoryParams(FUNCTION_MANDATORY_PARAMS);
}
if (options->IsFunctionEval()) {
ES2PANDA_ASSERT(iter != functionScopes_.end());
funcScope = *iter++;
auto scopeCtx = LexicalScope<FunctionScope>::Enter(this, funcScope);
AddMandatoryParams(ARROW_MANDATORY_PARAMS);
LookUpMandatoryReferences(funcScope, false);
}
for (; iter != functionScopes_.end(); iter++) {
funcScope = *iter;
const auto *scriptFunc = funcScope->Node()->AsScriptFunction();
auto scopeCtx = LexicalScope<FunctionScope>::Enter(this, funcScope);
if (!scriptFunc->IsArrow()) {
AddMandatoryParams(FUNCTION_MANDATORY_PARAMS);
continue;
}
const ir::ScriptFunction *ctor = util::Helpers::GetContainingConstructor(scriptFunc);
bool lexicalFunctionObject {};
if (ctor != nullptr && util::Helpers::GetClassDefinition(ctor)->Super() != nullptr &&
funcScope->HasFlag(ScopeFlags::USE_SUPER)) {
ES2PANDA_ASSERT(ctor->Scope()->HasFlag(ScopeFlags::INNER_ARROW));
ctor->Scope()->AddFlag(ScopeFlags::SET_LEXICAL_FUNCTION);
lexicalFunctionObject = true;
AddMandatoryParams(CTOR_ARROW_MANDATORY_PARAMS);
} else {
AddMandatoryParams(ARROW_MANDATORY_PARAMS);
}
LookUpMandatoryReferences(funcScope, lexicalFunctionObject);
}
}
}