* 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 "capturedVariables.h"
#include "util/options.h"
namespace ark::es2panda::compiler {
static bool OnLeftSideOfAssignment(ir::Identifier const *const ident) noexcept
{
return ident->Parent()->IsAssignmentExpression() && ident->Parent()->AsAssignmentExpression()->Left() == ident;
}
static void AddScopes(ir::AstNode *node, std::set<varbinder::Scope *> &scopes) noexcept
{
if (node->Scope()->IsFunctionScope()) {
scopes.emplace(node->Scope()->AsFunctionScope()->ParamScope());
}
if (node->Scope()->IsCatchScope()) {
scopes.emplace(node->Scope()->AsCatchScope()->ParamScope());
}
if (node->Scope()->IsLoopScope()) {
scopes.emplace(node->Scope()->AsLoopScope()->DeclScope());
}
scopes.emplace(node->Scope());
}
static varbinder::Variable *FindVariable(ir::Identifier *ident, std::set<varbinder::Scope *> const &scopes) noexcept
{
if (ident->IsErrorPlaceHolder()) {
return nullptr;
}
auto *var = ident->Variable();
if (var == nullptr) {
for (auto it = scopes.crbegin(); it != scopes.crend(); ++it) {
auto res = (*it)->Find(ident->Name(), varbinder::ResolveBindingOptions::VARIABLES);
if (res.variable != nullptr) {
var = res.variable;
break;
}
}
}
if (var != nullptr) {
auto *scope = var->GetScope();
ES2PANDA_ASSERT(scope != nullptr);
if (scopes.find(scope) != scopes.cend()) {
return nullptr;
}
}
return var;
}
static void FindModifiedCaptured(ir::ScriptFunction const *const scriptFunction,
std::set<varbinder::Variable *> &variables) noexcept
{
auto scopes = std::set<varbinder::Scope *> {};
bool inLambda = false;
std::function<void(ir::AstNode *)> walker = [&](ir::AstNode *node) -> void {
if (node->IsArrowFunctionExpression() || node->IsClassDeclaration()) {
auto savedWL = inLambda;
auto savedScopes = std::set<varbinder::Scope *> {};
std::swap(scopes, savedScopes);
inLambda = true;
node->Iterate(walker);
inLambda = savedWL;
std::swap(scopes, savedScopes);
savedScopes.clear();
return;
}
if (inLambda && node->IsScopeBearer()) {
AddScopes(node, scopes);
} else if (inLambda && node->IsIdentifier() && OnLeftSideOfAssignment(node->AsIdentifier())) {
if (auto *var = FindVariable(node->AsIdentifier(), scopes); var != nullptr) {
variables.insert(var);
}
}
node->Iterate(walker);
};
scriptFunction->Iterate(walker);
}
static void HandleScriptFunction(ir::ScriptFunction const *const scriptFunction) noexcept
{
auto variables = std::set<varbinder::Variable *> {};
FindModifiedCaptured(scriptFunction, variables);
for (auto *variable : variables) {
variable->AddFlag(varbinder::VariableFlags::CAPTURED_MODIFIED);
}
variables.clear();
}
bool CapturedVariables::PerformForProgram(parser::Program *program)
{
std::function<void(ir::AstNode *)> searchForFunctions = [&](ir::AstNode *ast) {
if (ast->IsScriptFunction()) {
HandleScriptFunction(ast->AsScriptFunction());
} else {
ast->Iterate(searchForFunctions);
}
};
program->Ast()->Iterate(searchForFunctions);
return true;
}
}