• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "localClassLowering.h"
17 #include "checker/ETSchecker.h"
18 #include "varbinder/ETSBinder.h"
19 #include "../util.h"
20 
21 namespace ark::es2panda::compiler {
22 
Name() const23 std::string_view LocalClassConstructionPhase::Name() const
24 {
25     return "LocalClassConstructionPhase";
26 }
27 
CreateCapturedField(checker::ETSChecker * checker,const varbinder::Variable * capturedVar,varbinder::ClassScope * scope,size_t & idx,const lexer::SourcePosition & pos)28 static ir::ClassProperty *CreateCapturedField(checker::ETSChecker *checker, const varbinder::Variable *capturedVar,
29                                               varbinder::ClassScope *scope, size_t &idx,
30                                               const lexer::SourcePosition &pos)
31 {
32     auto *allocator = checker->Allocator();
33     auto *varBinder = checker->VarBinder();
34 
35     // Enter the lambda class instance field scope, every property will be bound to the lambda instance itself
36     auto fieldCtx = varbinder::LexicalScope<varbinder::LocalScope>::Enter(varBinder, scope->InstanceFieldScope());
37 
38     // Create the name for the synthetic property node
39     util::UString fieldName(util::StringView("field#"), allocator);
40     fieldName.Append(std::to_string(idx));
41     // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
42     auto *fieldIdent = allocator->New<ir::Identifier>(fieldName.View(), allocator);
43 
44     // Create the synthetic class property node
45     auto *field =
46         allocator->New<ir::ClassProperty>(fieldIdent, nullptr, nullptr, ir::ModifierFlags::NONE, allocator, false);
47     fieldIdent->SetParent(field);
48 
49     // Add the declaration to the scope, and set the type based on the captured variable's scope
50     auto [decl, var] = varBinder->NewVarDecl<varbinder::LetDecl>(pos, fieldIdent->Name());
51     var->SetScope(scope->InstanceFieldScope());
52     var->AddFlag(varbinder::VariableFlags::PROPERTY);
53     var->SetTsType(capturedVar->TsType());
54 
55     fieldIdent->SetVariable(var);
56     field->SetTsType(capturedVar->TsType());
57     decl->BindNode(field);
58     return field;
59 }
60 
CreateCtorFieldInit(checker::ETSChecker * checker,util::StringView name,varbinder::Variable * var)61 static ir::Statement *CreateCtorFieldInit(checker::ETSChecker *checker, util::StringView name, varbinder::Variable *var)
62 {
63     // Create synthetic field initializers for the local class fields
64     // The node structure is the following: this.field0 = field0, where the left hand side refers to the local
65     // classes field, and the right hand side is refers to the constructors parameter
66     // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint)
67     auto *allocator = checker->Allocator();
68 
69     auto *thisExpr = allocator->New<ir::ThisExpression>();
70     auto *fieldAccessExpr = allocator->New<ir::Identifier>(name, allocator);
71     auto *leftHandSide = util::NodeAllocator::ForceSetParent<ir::MemberExpression>(
72         allocator, thisExpr, fieldAccessExpr, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
73     auto *rightHandSide = allocator->New<ir::Identifier>(name, allocator);
74     rightHandSide->SetVariable(var);
75     auto *initializer = util::NodeAllocator::ForceSetParent<ir::AssignmentExpression>(
76         allocator, leftHandSide, rightHandSide, lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
77     initializer->SetTsType(var->TsType());
78     return util::NodeAllocator::ForceSetParent<ir::ExpressionStatement>(allocator, initializer);
79 }
80 
CreateClassPropertiesForCapturedVariables(public_lib::Context * ctx,ir::ClassDefinition * classDef,ArenaSet<varbinder::Variable * > const & capturedVars,ArenaMap<varbinder::Variable *,varbinder::Variable * > & variableMap,ArenaMap<varbinder::Variable *,ir::ClassProperty * > & propertyMap)81 void LocalClassConstructionPhase::CreateClassPropertiesForCapturedVariables(
82     public_lib::Context *ctx, ir::ClassDefinition *classDef, ArenaSet<varbinder::Variable *> const &capturedVars,
83     ArenaMap<varbinder::Variable *, varbinder::Variable *> &variableMap,
84     ArenaMap<varbinder::Variable *, ir::ClassProperty *> &propertyMap)
85 {
86     checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
87     size_t idx = 0;
88     ArenaVector<ir::AstNode *> properties(ctx->allocator->Adapter());
89     for (auto var : capturedVars) {
90         ASSERT(classDef->Scope()->Type() == varbinder::ScopeType::CLASS);
91         auto *property = CreateCapturedField(checker, var, reinterpret_cast<varbinder::ClassScope *>(classDef->Scope()),
92                                              idx, classDef->Start());
93         LOG(DEBUG, ES2PANDA) << "  - Creating property (" << property->Id()->Name()
94                              << ") for captured variable: " << var->Name();
95         properties.push_back(property);
96         variableMap[var] = property->Id()->Variable();
97         propertyMap[var] = property;
98         idx++;
99     }
100 
101     classDef->AddProperties(std::move(properties));
102 }
103 
CreateParam(checker::ETSChecker * const checker,varbinder::FunctionParamScope * scope,util::StringView name,checker::Type * type)104 ir::ETSParameterExpression *LocalClassConstructionPhase::CreateParam(checker::ETSChecker *const checker,
105                                                                      varbinder::FunctionParamScope *scope,
106                                                                      util::StringView name, checker::Type *type)
107 {
108     auto newParam = checker->AddParam(name, nullptr);
109     newParam->SetTsType(type);
110     newParam->Ident()->SetTsType(type);
111     auto paramCtx = varbinder::LexicalScope<varbinder::FunctionParamScope>::Enter(checker->VarBinder(), scope, false);
112 
113     auto *paramVar = std::get<1>(checker->VarBinder()->AddParamDecl(newParam));
114     paramVar->SetTsType(newParam->TsType());
115     newParam->Ident()->SetVariable(paramVar);
116     return newParam;
117 }
118 
ModifyConstructorParameters(public_lib::Context * ctx,ir::ClassDefinition * classDef,ArenaSet<varbinder::Variable * > const & capturedVars,ArenaMap<varbinder::Variable *,varbinder::Variable * > & variableMap,ArenaMap<varbinder::Variable *,varbinder::Variable * > & parameterMap)119 void LocalClassConstructionPhase::ModifyConstructorParameters(
120     public_lib::Context *ctx, ir::ClassDefinition *classDef, ArenaSet<varbinder::Variable *> const &capturedVars,
121     ArenaMap<varbinder::Variable *, varbinder::Variable *> &variableMap,
122     ArenaMap<varbinder::Variable *, varbinder::Variable *> &parameterMap)
123 
124 {
125     auto *classType = classDef->TsType()->AsETSObjectType();
126     checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
127 
128     for (auto *signature : classType->ConstructSignatures()) {
129         LOG(DEBUG, ES2PANDA) << "  - Modifying Constructor: " << signature->InternalName();
130         auto constructor = signature->Function();
131         auto &parameters = constructor->Params();
132         auto &sigParams = signature->Params();
133         signature->GetSignatureInfo()->minArgCount += capturedVars.size();
134 
135         ASSERT(signature == constructor->Signature());
136         for (auto var : capturedVars) {
137             auto *newParam = CreateParam(checker, constructor->Scope()->ParamScope(), var->Name(), var->TsType());
138             newParam->SetParent(constructor);
139             // NOTE(psiket) : Moving the parameter after the 'this'. Should modify the AddParam
140             // to be able to insert after the this.
141             auto &paramScopeParams = constructor->Scope()->ParamScope()->Params();
142             auto thisParamIt = ++paramScopeParams.begin();
143             paramScopeParams.insert(thisParamIt, paramScopeParams.back());
144             paramScopeParams.pop_back();
145 
146             parameters.insert(parameters.begin(), newParam);
147             ASSERT(newParam->Variable()->Type() == varbinder::VariableType::LOCAL);
148             sigParams.insert(sigParams.begin(), newParam->Ident()->Variable()->AsLocalVariable());
149             parameterMap[var] = newParam->Ident()->Variable()->AsLocalVariable();
150         }
151         reinterpret_cast<varbinder::ETSBinder *>(checker->VarBinder())->BuildFunctionName(constructor);
152         LOG(DEBUG, ES2PANDA) << "    Transformed Constructor: " << signature->InternalName();
153 
154         auto *body = constructor->Body();
155         ArenaVector<ir::Statement *> initStatements(ctx->allocator->Adapter());
156         for (auto var : capturedVars) {
157             auto *propertyVar = variableMap[var];
158             auto *initStatement = CreateCtorFieldInit(checker, propertyVar->Name(), propertyVar);
159             auto *fieldInit = initStatement->AsExpressionStatement()->GetExpression()->AsAssignmentExpression();
160             auto *ctorParamVar = parameterMap[var];
161             auto *fieldVar = variableMap[var];
162             auto *leftHandSide = fieldInit->Left();
163             leftHandSide->AsMemberExpression()->SetObjectType(classType);
164             leftHandSide->AsMemberExpression()->SetPropVar(fieldVar->AsLocalVariable());
165             leftHandSide->AsMemberExpression()->SetIgnoreBox();
166             leftHandSide->AsMemberExpression()->SetTsType(fieldVar->TsType());
167             leftHandSide->AsMemberExpression()->Object()->SetTsType(classType);
168             fieldInit->Right()->AsIdentifier()->SetVariable(ctorParamVar);
169             fieldInit->Right()->SetTsType(ctorParamVar->TsType());
170             initStatement->SetParent(body);
171             initStatements.push_back(initStatement);
172         }
173         auto &statements = body->AsBlockStatement()->Statements();
174         statements.insert(statements.begin(), initStatements.begin(), initStatements.end());
175     }
176 }
177 
RemapReferencesFromCapturedVariablesToClassProperties(ir::ClassDefinition * classDef,ArenaMap<varbinder::Variable *,varbinder::Variable * > & variableMap)178 void LocalClassConstructionPhase::RemapReferencesFromCapturedVariablesToClassProperties(
179     ir::ClassDefinition *classDef, ArenaMap<varbinder::Variable *, varbinder::Variable *> &variableMap)
180 {
181     auto *classType = classDef->TsType()->AsETSObjectType();
182     auto remapCapturedVariables = [&variableMap](ir::AstNode *childNode) {
183         if (childNode->Type() == ir::AstNodeType::IDENTIFIER) {
184             LOG(DEBUG, ES2PANDA) << "    checking var:" << (void *)childNode;
185             const auto &mapIt = variableMap.find(childNode->AsIdentifier()->Variable());
186             if (mapIt != variableMap.end()) {
187                 LOG(DEBUG, ES2PANDA) << "      Remap: " << childNode->AsIdentifier()->Name()
188                                      << " (identifier:" << (void *)childNode
189                                      << ") variable:" << (void *)childNode->AsIdentifier()->Variable()
190                                      << " -> property variable:" << (void *)mapIt->second;
191                 childNode->AsIdentifier()->SetVariable(mapIt->second);
192             } else {
193             }
194         }
195     };
196 
197     for (auto *it : classDef->Body()) {
198         if (it->IsMethodDefinition() && !it->AsMethodDefinition()->IsConstructor()) {
199             LOG(DEBUG, ES2PANDA) << "  - Rebinding variable rerferences in: "
200                                  << it->AsMethodDefinition()->Id()->Name().Mutf8().c_str();
201             it->AsMethodDefinition()->Function()->Body()->IterateRecursively(remapCapturedVariables);
202         }
203     }
204     // Since the constructor with zero parameter is not listed in the class_def body the constructors
205     // processed separately
206     for (auto *signature : classType->ConstructSignatures()) {
207         auto *constructor = signature->Function();
208         LOG(DEBUG, ES2PANDA) << "  - Rebinding variable rerferences in: " << constructor->Id()->Name();
209         constructor->Body()->IterateRecursively(remapCapturedVariables);
210     }
211 }
212 
HandleLocalClass(public_lib::Context * ctx,ArenaUnorderedMap<ir::ClassDefinition *,ArenaSet<varbinder::Variable * >> & capturedVarsMap,ir::ClassDefinition * classDef)213 void LocalClassConstructionPhase::HandleLocalClass(
214     public_lib::Context *ctx,
215     ArenaUnorderedMap<ir::ClassDefinition *, ArenaSet<varbinder::Variable *>> &capturedVarsMap,
216     ir::ClassDefinition *classDef)
217 {
218     LOG(DEBUG, ES2PANDA) << "Altering local class with the captured variables: " << classDef->InternalName();
219     auto capturedVars = FindCaptured(ctx->allocator, classDef);
220     // Map the captured variable to the variable of the class property
221     ArenaMap<varbinder::Variable *, varbinder::Variable *> variableMap(ctx->allocator->Adapter());
222     // Map the captured variable to the class property
223     ArenaMap<varbinder::Variable *, ir::ClassProperty *> propertyMap(ctx->allocator->Adapter());
224     // Map the captured variable to the constructor parameter
225     ArenaMap<varbinder::Variable *, varbinder::Variable *> parameterMap(ctx->allocator->Adapter());
226 
227     CreateClassPropertiesForCapturedVariables(ctx, classDef, capturedVars, variableMap, propertyMap);
228     ModifyConstructorParameters(ctx, classDef, capturedVars, variableMap, parameterMap);
229     RemapReferencesFromCapturedVariablesToClassProperties(classDef, variableMap);
230     capturedVarsMap.emplace(classDef, std::move(capturedVars));
231 }
232 
Perform(public_lib::Context * ctx,parser::Program * program)233 bool LocalClassConstructionPhase::Perform(public_lib::Context *ctx, parser::Program *program)
234 {
235     checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
236     ArenaUnorderedMap<ir::ClassDefinition *, ArenaSet<varbinder::Variable *>> capturedVarsMap {
237         ctx->allocator->Adapter()};
238 
239     program->Ast()->IterateRecursivelyPostorder([&](ir::AstNode *ast) {
240         if (ast->IsClassDefinition() && ast->AsClassDefinition()->IsLocal()) {
241             HandleLocalClass(ctx, capturedVarsMap, ast->AsClassDefinition());
242         }
243     });
244 
245     // Alter the instantiations
246     auto handleLocalClassInstantiation = [ctx, checker, &capturedVarsMap](ir::ClassDefinition *classDef,
247                                                                           ir::ETSNewClassInstanceExpression *newExpr) {
248         LOG(DEBUG, ES2PANDA) << "Instantiating local class: " << classDef->Ident()->Name();
249         auto capturedVarsIt = capturedVarsMap.find(classDef);
250         ASSERT(capturedVarsIt != capturedVarsMap.cend());
251         auto &capturedVars = capturedVarsIt->second;
252         for (auto *var : capturedVars) {
253             LOG(DEBUG, ES2PANDA) << "  - Extending constructor argument with captured variable: " << var->Name();
254 
255             auto *param = checker->AllocNode<ir::Identifier>(var->Name(), ctx->allocator);
256             param->SetVariable(var);
257             param->SetIgnoreBox();
258             param->SetTsType(param->Variable()->TsType());
259             param->SetParent(newExpr);
260             newExpr->AddToArgumentsFront(param);
261         }
262     };
263 
264     program->Ast()->IterateRecursivelyPostorder([&](ir::AstNode *ast) {
265         if (ast->IsETSNewClassInstanceExpression()) {
266             auto *newExpr = ast->AsETSNewClassInstanceExpression();
267             checker::Type *calleeType = newExpr->GetTypeRef()->Check(checker);
268             auto *calleeObj = calleeType->AsETSObjectType();
269             if (!calleeObj->GetDeclNode()->IsClassDefinition()) {
270                 return;
271             }
272             auto *classDef = calleeObj->GetDeclNode()->AsClassDefinition();
273             if (classDef->IsLocal()) {
274                 handleLocalClassInstantiation(classDef, newExpr);
275             }
276         }
277     });
278 
279     return true;
280 }
281 
282 }  // namespace ark::es2panda::compiler
283