1 /*
2 * Copyright (c) 2024-2025 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 "interfaceObjectLiteralLowering.h"
17 #include "checker/ETSchecker.h"
18 #include "checker/ets/typeRelationContext.h"
19 #include "compiler/lowering/util.h"
20 #include "generated/signatures.h"
21 #include "ir/expressions/assignmentExpression.h"
22 #include "util/helpers.h"
23
24 namespace ark::es2panda::compiler {
25
26 static constexpr std::string_view OBJECT_LITERAL_SUFFIX = "$ObjectLiteral";
27 using ReadonlyFieldHolder =
28 std::tuple<util::UString, util::StringView, checker::Type *>; // anonClassFieldName, paramName, fieldType
29
Name() const30 std::string_view InterfaceObjectLiteralLowering::Name() const
31 {
32 return "InterfaceObjectLiteralLowering";
33 }
34
IsInterfaceType(const checker::Type * type)35 static inline bool IsInterfaceType(const checker::Type *type)
36 {
37 return type != nullptr && type->IsETSObjectType() &&
38 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::INTERFACE) &&
39 !type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC);
40 }
41
IsAbstractClassType(const checker::Type * type)42 static inline bool IsAbstractClassType(const checker::Type *type)
43 {
44 return type != nullptr && type->IsETSObjectType() &&
45 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::ABSTRACT) &&
46 !type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC);
47 }
48
CreateAnonClassImplCtor(public_lib::Context * ctx,ArenaVector<ReadonlyFieldHolder> & readonlyFields)49 static ir::AstNode *CreateAnonClassImplCtor(public_lib::Context *ctx, ArenaVector<ReadonlyFieldHolder> &readonlyFields)
50 {
51 auto *const checker = ctx->checker->AsETSChecker();
52 auto *const parser = ctx->parser->AsETSParser();
53 checker::ETSChecker::ClassInitializerBuilder initBuilder =
54 [ctx, checker, parser, readonlyFields](ArenaVector<ir::Statement *> *statements,
55 ArenaVector<ir::Expression *> *params) {
56 for (auto [anonClassFieldName, paramName, retType] : readonlyFields) {
57 ir::ETSParameterExpression *param =
58 checker->AddParam(paramName, ctx->AllocNode<ir::OpaqueTypeNode>(retType, ctx->Allocator()));
59 params->push_back(param);
60 auto *paramIdent = ctx->AllocNode<ir::Identifier>(paramName, ctx->Allocator());
61 statements->push_back(
62 parser->CreateFormattedStatement("this.@@I1 = @@I2;", anonClassFieldName, paramIdent));
63 }
64 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
65 };
66
67 return checker->CreateClassInstanceInitializer(initBuilder);
68 }
69
CreateAnonClassField(public_lib::Context * ctx,ir::MethodDefinition * ifaceMethod,util::UString anonClassFieldName)70 static ir::ClassProperty *CreateAnonClassField(public_lib::Context *ctx, ir::MethodDefinition *ifaceMethod,
71 util::UString anonClassFieldName)
72 {
73 auto *const parser = ctx->parser->AsETSParser();
74 // Field type annotation
75 ES2PANDA_ASSERT(ifaceMethod->Function());
76 auto *fieldType = ifaceMethod->Function()->Signature()->ReturnType();
77
78 std::stringstream sourceCode;
79 // Field modifiers flags
80 sourceCode << "private ";
81 // No overloads means no setter function with the same name, so the field is readonly
82 if (ifaceMethod->Overloads().empty()) {
83 sourceCode << "readonly ";
84 }
85 sourceCode << "@@I1 : @@T2;" << std::endl;
86
87 auto field = parser->CreateFormattedClassFieldDefinition(sourceCode.str(), anonClassFieldName, fieldType);
88 field->SetRange(ifaceMethod->Range());
89
90 return field->AsClassProperty();
91 }
92
CreateAnonClassFieldGetterSetter(public_lib::Context * ctx,ir::MethodDefinition * ifaceMethod,bool isSetter,util::UString anonClassFieldName)93 static ir::MethodDefinition *CreateAnonClassFieldGetterSetter(public_lib::Context *ctx,
94 ir::MethodDefinition *ifaceMethod, bool isSetter,
95 util::UString anonClassFieldName)
96 {
97 auto *const parser = ctx->parser->AsETSParser();
98 // Field type annotation
99 auto *fieldType = ifaceMethod->Function()->Signature()->ReturnType();
100 ES2PANDA_ASSERT(fieldType != nullptr);
101
102 std::stringstream sourceCode;
103
104 if (isSetter) {
105 // Setter body: this.<fieldName> = <callParam>;
106 sourceCode << "public set @@I1 (anonParam:@@T2){" << std::endl;
107 sourceCode << "this.@@I3 = anonParam" << std::endl;
108 sourceCode << "}" << std::endl;
109 ES2PANDA_ASSERT(ifaceMethod->Id());
110 return parser
111 ->CreateFormattedClassMethodDefinition(sourceCode.str(), ifaceMethod->Id()->Name(), fieldType,
112 anonClassFieldName)
113 ->AsMethodDefinition();
114 }
115
116 // Getter body: return this.<fieldName>;
117 sourceCode << "public get @@I1():@@T2{" << std::endl;
118 sourceCode << "return this.@@I3" << std::endl;
119 sourceCode << "}" << std::endl;
120
121 return parser
122 ->CreateFormattedClassMethodDefinition(sourceCode.str(), ifaceMethod->Id()->Name(), fieldType,
123 anonClassFieldName)
124 ->AsMethodDefinition();
125 }
126
FillClassBody(public_lib::Context * ctx,ArenaVector<ir::AstNode * > * classBody,const ArenaVector<ir::AstNode * > & ifaceBody,ArenaVector<ReadonlyFieldHolder> & readonlyFields,checker::ETSObjectType * currentType=nullptr)127 static void FillClassBody(public_lib::Context *ctx, ArenaVector<ir::AstNode *> *classBody,
128 const ArenaVector<ir::AstNode *> &ifaceBody, ArenaVector<ReadonlyFieldHolder> &readonlyFields,
129 checker::ETSObjectType *currentType = nullptr)
130 {
131 for (auto *it : ifaceBody) {
132 ES2PANDA_ASSERT(it->IsMethodDefinition());
133 auto *ifaceMethod = it->AsMethodDefinition();
134
135 ES2PANDA_ASSERT(ifaceMethod->Function());
136 if (!ifaceMethod->Function()->IsGetter()) {
137 continue;
138 }
139
140 auto iter = std::find_if(classBody->begin(), classBody->end(), [ifaceMethod](ir::AstNode *ast) -> bool {
141 return ast->IsMethodDefinition() && ast->AsMethodDefinition()->Function()->IsGetter() &&
142 ast->AsMethodDefinition()->Id()->Name() == ifaceMethod->Id()->Name();
143 });
144 if (iter != classBody->end()) {
145 continue;
146 }
147
148 auto copyIfaceMethod = ifaceMethod->Clone(ctx->Allocator(), nullptr);
149 copyIfaceMethod->SetRange(ifaceMethod->Range());
150 copyIfaceMethod->Function()->SetSignature(ifaceMethod->Function()->Signature());
151
152 if (currentType != nullptr) {
153 auto instanProp =
154 currentType->GetOwnProperty<checker::PropertyType::INSTANCE_METHOD>(ifaceMethod->Id()->Name());
155 auto funcType = (instanProp != nullptr) ? instanProp->TsType() : nullptr;
156 if (funcType != nullptr) {
157 ES2PANDA_ASSERT(funcType->IsETSFunctionType() &&
158 funcType->AsETSFunctionType()->FindGetter() != nullptr);
159 copyIfaceMethod->Function()->SetSignature(funcType->AsETSFunctionType()->FindGetter());
160 }
161 }
162
163 // Field identifier
164 util::UString anonClassFieldName(
165 std::string(compiler::Signatures::PROPERTY) + ifaceMethod->Id()->Name().Mutf8(), ctx->allocator);
166 auto *field = CreateAnonClassField(ctx, copyIfaceMethod, anonClassFieldName);
167 if (field->IsReadonly()) {
168 readonlyFields.push_back(
169 std::make_tuple(anonClassFieldName, ifaceMethod->Id()->Name(), field->TypeAnnotation()->TsType()));
170 }
171 classBody->push_back(field);
172 SetSourceRangesRecursively(field, ifaceMethod->Range());
173
174 auto *getter = CreateAnonClassFieldGetterSetter(ctx, copyIfaceMethod, false, anonClassFieldName);
175 classBody->push_back(getter);
176 SetSourceRangesRecursively(getter, ifaceMethod->Range());
177
178 if (copyIfaceMethod->Overloads().size() == 1 && copyIfaceMethod->Overloads()[0]->Function()->IsSetter()) {
179 auto *setter = CreateAnonClassFieldGetterSetter(ctx, copyIfaceMethod, true, anonClassFieldName);
180 classBody->push_back(setter);
181 SetSourceRangesRecursively(setter, ifaceMethod->Range());
182 }
183 }
184 }
185
186 // CC-OFFNXT(G.FUN.01-CPP) solid logic
FillAnonClassBody(public_lib::Context * ctx,ArenaVector<ir::AstNode * > * classBody,ir::TSInterfaceDeclaration * ifaceNode,ArenaVector<ReadonlyFieldHolder> & readonlyFields,checker::ETSObjectType * interfaceType=nullptr)187 static void FillAnonClassBody(public_lib::Context *ctx, ArenaVector<ir::AstNode *> *classBody,
188 ir::TSInterfaceDeclaration *ifaceNode, ArenaVector<ReadonlyFieldHolder> &readonlyFields,
189 checker::ETSObjectType *interfaceType = nullptr)
190 {
191 FillClassBody(ctx, classBody, ifaceNode->Body()->Body(), readonlyFields, interfaceType);
192 for (auto *extendedIface : ifaceNode->TsType()->AsETSObjectType()->Interfaces()) {
193 FillAnonClassBody(ctx, classBody, extendedIface->GetDeclNode()->AsTSInterfaceDeclaration(), readonlyFields,
194 extendedIface);
195 }
196 }
197
198 // Annotate synthetic class so we can determite it's origin in a runtime
199 // Now implemented for the anon class generated from an interface only
AnnotateGeneratedAnonClass(checker::ETSChecker * checker,ir::ClassDefinition * classDef)200 static void AnnotateGeneratedAnonClass(checker::ETSChecker *checker, ir::ClassDefinition *classDef)
201 {
202 auto *annoId =
203 checker->ProgramAllocNode<ir::Identifier>(Signatures::INTERFACE_OBJ_LITERAL, checker->ProgramAllocator());
204 annoId->SetAnnotationUsage();
205 auto *annoUsage = checker->ProgramAllocNode<ir::AnnotationUsage>(annoId, checker->ProgramAllocator());
206 ES2PANDA_ASSERT(annoUsage);
207 annoUsage->AddModifier(ir::ModifierFlags::ANNOTATION_USAGE);
208 annoUsage->SetParent(classDef);
209 annoId->SetParent(annoUsage);
210 classDef->Annotations().emplace_back(annoUsage);
211 RefineSourceRanges(annoUsage);
212 CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, annoUsage);
213 }
214
GenerateAnonClassTypeFromInterface(public_lib::Context * ctx,ir::TSInterfaceDeclaration * ifaceNode)215 static void GenerateAnonClassTypeFromInterface(public_lib::Context *ctx, ir::TSInterfaceDeclaration *ifaceNode)
216 {
217 auto *checker = ctx->checker->AsETSChecker();
218
219 if (ifaceNode->GetAnonClass() != nullptr) {
220 return;
221 }
222
223 auto classBodyBuilder = [ctx, checker, ifaceNode](ArenaVector<ir::AstNode *> *classBody) {
224 if (ifaceNode->TsType() == nullptr) {
225 ifaceNode->Check(checker);
226 }
227 ArenaVector<ReadonlyFieldHolder> readonlyFields(ctx->Allocator()->Adapter());
228 FillAnonClassBody(ctx, classBody, ifaceNode, readonlyFields);
229 classBody->push_back(CreateAnonClassImplCtor(ctx, readonlyFields));
230 };
231
232 auto originalName = std::string {ifaceNode->InternalName()};
233 std::replace(originalName.begin(), originalName.end(), '.', '$');
234 auto anonClassName = util::UString(originalName.append(OBJECT_LITERAL_SUFFIX), checker->ProgramAllocator());
235 auto *classDecl = checker->BuildClass(anonClassName.View(), classBodyBuilder);
236 RefineSourceRanges(classDecl);
237 auto *classDef = classDecl->Definition();
238 auto *classType = classDef->TsType()->AsETSObjectType();
239 classDef->SetAnonymousModifier();
240
241 classDecl->SetRange(ifaceNode->Range());
242 classDef->SetRange(ifaceNode->Range());
243
244 AnnotateGeneratedAnonClass(checker, classDef);
245
246 // Class type params
247 if (ifaceNode->TypeParams() != nullptr) {
248 ArenaVector<checker::Type *> typeArgs(ctx->Allocator()->Adapter());
249 for (auto param : ifaceNode->TypeParams()->Params()) {
250 auto *var = param->Name()->Variable();
251 ES2PANDA_ASSERT(var && var->TsType()->IsETSTypeParameter());
252 typeArgs.push_back(var->TsType());
253 }
254 classType->SetTypeArguments(std::move(typeArgs));
255 }
256
257 // Class implements
258 auto *classImplements = ctx->AllocNode<ir::TSClassImplements>(
259 ctx->AllocNode<ir::OpaqueTypeNode>(ifaceNode->TsType(), ctx->Allocator()));
260 ES2PANDA_ASSERT(classImplements);
261 classImplements->SetParent(classDef);
262 classDef->Implements().emplace_back(classImplements);
263 classType->RemoveObjectFlag(checker::ETSObjectFlags::RESOLVED_INTERFACES);
264 checker->GetInterfacesOfClass(classType);
265
266 ifaceNode->SetAnonClass(classDecl);
267 }
268
GenerateAnonClassTypeFromAbstractClass(public_lib::Context * ctx,ir::ClassDefinition * abstractClassNode)269 static void GenerateAnonClassTypeFromAbstractClass(public_lib::Context *ctx, ir::ClassDefinition *abstractClassNode)
270 {
271 auto *checker = ctx->checker->AsETSChecker();
272
273 if (abstractClassNode->GetAnonClass() != nullptr) {
274 return;
275 }
276
277 auto classBodyBuilder = [checker](ArenaVector<ir::AstNode *> *classBody) {
278 checker::ETSChecker::ClassInitializerBuilder initBuilder =
279 [checker]([[maybe_unused]] ArenaVector<ir::Statement *> *statements,
280 [[maybe_unused]] ArenaVector<ir::Expression *> *params) {
281 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
282 };
283
284 auto ctor = checker->CreateClassInstanceInitializer(initBuilder);
285 classBody->push_back(ctor);
286 };
287
288 auto originalName = std::string {abstractClassNode->InternalName()};
289 std::replace(originalName.begin(), originalName.end(), '.', '$');
290 auto anonClassName = util::UString(originalName.append(OBJECT_LITERAL_SUFFIX), checker->ProgramAllocator());
291 auto *classDecl = checker->BuildClass(anonClassName.View(), classBodyBuilder);
292 RefineSourceRanges(classDecl);
293 auto *classDef = classDecl->Definition();
294 auto *classType = classDef->TsType()->AsETSObjectType();
295
296 classDecl->SetRange(abstractClassNode->Range());
297 classDef->SetAnonymousModifier();
298 classDef->SetRange(abstractClassNode->Range());
299
300 // Class type params
301 if (abstractClassNode->TypeParams() != nullptr) {
302 ArenaVector<checker::Type *> typeArgs(ctx->Allocator()->Adapter());
303 for (auto param : abstractClassNode->TypeParams()->Params()) {
304 auto *var = param->Name()->Variable();
305 ES2PANDA_ASSERT(var && var->TsType()->IsETSTypeParameter());
306 typeArgs.push_back(var->TsType());
307 }
308 classType->SetTypeArguments(std::move(typeArgs));
309 }
310
311 abstractClassNode->SetAnonClass(classDecl);
312 classType->SetSuperType(abstractClassNode->TsType()->AsETSObjectType());
313 }
314
ProcessDeclNode(checker::ETSChecker * checker,checker::ETSObjectType * targetType,ir::ObjectExpression * objExpr)315 static checker::Type *ProcessDeclNode(checker::ETSChecker *checker, checker::ETSObjectType *targetType,
316 ir::ObjectExpression *objExpr)
317 {
318 auto *declNode = targetType->GetDeclNode();
319
320 if (declNode->IsTSInterfaceDeclaration()) {
321 auto *ifaceNode = declNode->AsTSInterfaceDeclaration();
322 if (ifaceNode->GetAnonClass() == nullptr) {
323 checker->LogError(diagnostic::INTERFACE_WITH_METHOD, {}, objExpr->Start());
324 return checker->GlobalTypeError();
325 }
326 return ifaceNode->GetAnonClass()->Definition()->TsType();
327 }
328
329 auto *classDef = declNode->AsClassDefinition();
330 ES2PANDA_ASSERT(classDef->IsAbstract());
331
332 if (classDef->GetAnonClass() == nullptr) {
333 for (auto it : classDef->Body()) {
334 if (!it->IsMethodDefinition() || !it->AsMethodDefinition()->IsAbstract()) {
335 continue;
336 }
337
338 ES2PANDA_ASSERT(it->AsMethodDefinition()->Id());
339 checker->LogError(diagnostic::ABSTRACT_METH_IN_ABSTRACT_CLASS, {it->AsMethodDefinition()->Id()->Name()},
340 objExpr->Start());
341 return checker->GlobalTypeError();
342 }
343 }
344 return classDef->GetAnonClass()->Definition()->TsType();
345 }
346
HandleInterfaceLowering(public_lib::Context * ctx,ir::ObjectExpression * objExpr)347 static void HandleInterfaceLowering(public_lib::Context *ctx, ir::ObjectExpression *objExpr)
348 {
349 auto *checker = ctx->checker->AsETSChecker();
350 auto *targetType = objExpr->TsType();
351 checker->CheckObjectLiteralKeys(objExpr->Properties());
352
353 auto *etsTargetType = targetType->AsETSObjectType();
354 checker::Type *resultType = ProcessDeclNode(checker, etsTargetType, objExpr);
355
356 if (resultType->IsTypeError()) {
357 objExpr->SetTsType(resultType);
358 return;
359 }
360
361 if (etsTargetType->IsPartial()) {
362 resultType->AsETSObjectType()->SetBaseType(etsTargetType->GetBaseType());
363 }
364
365 if (!etsTargetType->TypeArguments().empty()) {
366 ArenaVector<checker::Type *> typeArgTypes(etsTargetType->TypeArguments());
367 checker::InstantiationContext instantiationCtx(checker, resultType->AsETSObjectType(), std::move(typeArgTypes),
368 objExpr->Start());
369 resultType = instantiationCtx.Result();
370 }
371
372 if (const auto *const parent = objExpr->Parent(); parent->IsArrayExpression()) {
373 for (auto *elem : parent->AsArrayExpression()->Elements()) {
374 if (elem->IsObjectExpression()) {
375 elem->AsObjectExpression()->SetTsType(resultType);
376 }
377 }
378 }
379 objExpr->SetTsType(resultType);
380 }
381
CheckInterfaceShouldGenerateAnonClass(ir::TSInterfaceDeclaration * interfaceDecl)382 static bool CheckInterfaceShouldGenerateAnonClass(ir::TSInterfaceDeclaration *interfaceDecl)
383 {
384 for (auto it : interfaceDecl->Body()->Body()) {
385 ES2PANDA_ASSERT(it->IsMethodDefinition());
386 auto methodDef = it->AsMethodDefinition();
387 ES2PANDA_ASSERT(methodDef->Function());
388 if (!methodDef->Function()->IsGetter() && !methodDef->Function()->IsSetter()) {
389 return false;
390 }
391 }
392
393 return true;
394 }
395
CheckAbstractClassShouldGenerateAnonClass(ir::ClassDefinition * classDef)396 static bool CheckAbstractClassShouldGenerateAnonClass(ir::ClassDefinition *classDef)
397 {
398 auto constructorSigs = classDef->TsType()->AsETSObjectType()->ConstructSignatures();
399 if (auto res = std::find_if(constructorSigs.cbegin(), constructorSigs.cend(),
400 [](checker::Signature *sig) -> bool { return sig->MinArgCount() == 0; });
401 res == constructorSigs.cend()) {
402 return false;
403 }
404 for (auto it : classDef->Body()) {
405 if (it->IsMethodDefinition() && it->AsMethodDefinition()->IsAbstract()) {
406 return false;
407 }
408 }
409
410 return true;
411 }
412
TransfromInterfaceDecl(public_lib::Context * ctx,parser::Program * program)413 static void TransfromInterfaceDecl(public_lib::Context *ctx, parser::Program *program)
414 {
415 program->Ast()->IterateRecursivelyPostorder([ctx, program](ir::AstNode *ast) -> void {
416 if (ast->IsTSInterfaceDeclaration() && CheckInterfaceShouldGenerateAnonClass(ast->AsTSInterfaceDeclaration())) {
417 GenerateAnonClassTypeFromInterface(ctx, ast->AsTSInterfaceDeclaration());
418 } else if (ast->IsClassDefinition() && ast != program->GlobalClass() &&
419 ast->AsClassDefinition()->IsAbstract() &&
420 CheckAbstractClassShouldGenerateAnonClass(ast->AsClassDefinition())) {
421 GenerateAnonClassTypeFromAbstractClass(ctx, ast->AsClassDefinition());
422 }
423 });
424 }
425
TransfromInterfaceLiteral(public_lib::Context * ctx,parser::Program * program)426 static void TransfromInterfaceLiteral(public_lib::Context *ctx, parser::Program *program)
427 {
428 program->Ast()->IterateRecursivelyPostorder([ctx](ir::AstNode *ast) -> void {
429 if (ast->IsObjectExpression() && (IsInterfaceType(ast->AsObjectExpression()->TsType()) ||
430 IsAbstractClassType(ast->AsObjectExpression()->TsType()))) {
431 HandleInterfaceLowering(ctx, ast->AsObjectExpression());
432 }
433 });
434 }
435
Perform(public_lib::Context * ctx,parser::Program * program)436 bool InterfaceObjectLiteralLowering::Perform(public_lib::Context *ctx, parser::Program *program)
437 {
438 auto *varbinder = program->VarBinder()->AsETSBinder();
439 for (auto &[_, extPrograms] : program->ExternalSources()) {
440 (void)_;
441 for (auto *extProg : extPrograms) {
442 auto *savedProgram = varbinder->Program();
443 auto *savedRecordTable = varbinder->GetRecordTable();
444 auto *savedTopScope = varbinder->TopScope();
445 varbinder->ResetTopScope(extProg->GlobalScope());
446 varbinder->SetRecordTable(varbinder->GetExternalRecordTable().at(extProg));
447 varbinder->SetProgram(extProg);
448 TransfromInterfaceDecl(ctx, extProg);
449 varbinder->SetProgram(savedProgram);
450 varbinder->SetRecordTable(savedRecordTable);
451 varbinder->ResetTopScope(savedTopScope);
452 }
453 }
454
455 TransfromInterfaceDecl(ctx, program);
456
457 for (auto &[_, extPrograms] : program->ExternalSources()) {
458 (void)_;
459 for (auto *extProg : extPrograms) {
460 TransfromInterfaceLiteral(ctx, extProg);
461 }
462 }
463
464 TransfromInterfaceLiteral(ctx, program);
465
466 return true;
467 }
468
469 } // namespace ark::es2panda::compiler
470