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 *> ¶meterMap)
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 ¶meters = 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 ¶mScopeParams = 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