1 /*
2 * Copyright (c) 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 "interfaceObjectLiteralLowering.h"
17 #include "checker/ETSchecker.h"
18 #include "checker/ets/typeRelationContext.h"
19 #include "ir/expressions/assignmentExpression.h"
20 #include "util/helpers.h"
21
22 namespace ark::es2panda::compiler {
23
Name() const24 std::string_view InterfaceObjectLiteralLowering::Name() const
25 {
26 return "InterfaceObjectLiteralLowering";
27 }
28
IsInterfaceType(const checker::Type *type)29 static inline bool IsInterfaceType(const checker::Type *type)
30 {
31 return type != nullptr && type->IsETSObjectType() &&
32 type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::INTERFACE) &&
33 !type->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::DYNAMIC);
34 }
35
CreateAnonClassImplCtor(checker::ETSChecker *checker)36 static ir::AstNode *CreateAnonClassImplCtor(checker::ETSChecker *checker)
37 {
38 checker::ETSChecker::ClassInitializerBuilder initBuilder =
39 [checker]([[maybe_unused]] ArenaVector<ir::Statement *> *statements,
40 [[maybe_unused]] ArenaVector<ir::Expression *> *params) {
41 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
42 };
43
44 return checker->CreateClassInstanceInitializer(initBuilder);
45 }
46
CreateAnonClassField(ir::MethodDefinition *ifaceMethod, checker::ETSChecker *checker)47 static ir::ClassProperty *CreateAnonClassField(ir::MethodDefinition *ifaceMethod, checker::ETSChecker *checker)
48 {
49 auto *const allocator = checker->Allocator();
50
51 // Field type annotation
52 auto *fieldType = ifaceMethod->Function()->Signature()->ReturnType();
53 if (IsInterfaceType(fieldType)) {
54 auto *anonClass = fieldType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration()->GetAnonClass();
55 ASSERT(anonClass != nullptr);
56 fieldType = anonClass->Definition()->TsType();
57 }
58 ASSERT(fieldType != nullptr);
59 auto *fieldTypeNode = checker->AllocNode<ir::OpaqueTypeNode>(fieldType);
60
61 // Field identifier
62 util::UString fieldName(std::string("_"), allocator);
63 fieldName.Append(ifaceMethod->Id()->Name());
64 auto *fieldId = checker->AllocNode<ir::Identifier>(fieldName.View(), nullptr, allocator);
65
66 // Field modifiers flags
67 ir::ModifierFlags fieldMF = ir::ModifierFlags::PRIVATE;
68
69 // No overloads means no setter function with the same name, so the field is readonly
70 if (ifaceMethod->Overloads().empty()) {
71 fieldMF |= ir::ModifierFlags::READONLY;
72 }
73
74 // Create synthetic class property node
75 auto *field = checker->AllocNode<ir::ClassProperty>(fieldId, nullptr, fieldTypeNode->Clone(allocator, nullptr),
76 fieldMF, allocator, false);
77 field->SetRange(ifaceMethod->Range());
78
79 return field;
80 }
81
CreateAnonClassFieldGetterSetter(checker::ETSChecker *checker, ir::MethodDefinition *ifaceMethod, bool isSetter)82 static ir::MethodDefinition *CreateAnonClassFieldGetterSetter(checker::ETSChecker *checker,
83 ir::MethodDefinition *ifaceMethod, bool isSetter)
84 {
85 checker::ETSChecker::MethodBuilder methodBuilder = [checker, ifaceMethod,
86 isSetter](ArenaVector<ir::Statement *> *statements,
87 ArenaVector<ir::Expression *> *params,
88 checker::Type **returnType) {
89 auto *const allocator = checker->Allocator();
90
91 // Adding mandatory 'this' parameter
92 checker->AddParam(varbinder::VarBinder::MANDATORY_PARAM_THIS, nullptr);
93
94 // ifaceMethod is getter, so it should have return type
95 auto *retType = ifaceMethod->Function()->Signature()->ReturnType();
96 if (IsInterfaceType(retType)) {
97 auto *anonClass = retType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration()->GetAnonClass();
98 ASSERT(anonClass != nullptr);
99 retType = anonClass->Definition()->TsType();
100 }
101 ASSERT(retType != nullptr);
102
103 // Field identifier
104 util::UString fieldName(std::string("_"), allocator);
105 fieldName.Append(ifaceMethod->Id()->Name());
106 auto *fieldId = checker->AllocNode<ir::Identifier>(fieldName.View(), nullptr, allocator);
107
108 if (isSetter) {
109 // Setter call params
110 ir::ETSParameterExpression *param =
111 checker->AddParam(ifaceMethod->Id()->Name(), checker->AllocNode<ir::OpaqueTypeNode>(retType));
112 params->push_back(param);
113
114 // Setter body:
115 // this.<fieldName> = <callParam>;
116 auto *thisExpr = checker->AllocNode<ir::ThisExpression>();
117 auto *lhs = checker->AllocNode<ir::MemberExpression>(
118 thisExpr, fieldId->Clone(allocator, nullptr), ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
119 auto *rhs = param->Ident()->Clone(allocator, nullptr);
120
121 auto *assignment =
122 checker->AllocNode<ir::AssignmentExpression>(lhs, rhs, lexer::TokenType::PUNCTUATOR_SUBSTITUTION);
123 auto *statement = checker->AllocNode<ir::ExpressionStatement>(assignment);
124 statements->push_back(statement);
125
126 // Setter return type
127 *returnType = checker->GlobalVoidType();
128 } else {
129 // Getter call params are empty
130
131 // Getter body:
132 // Just return this.<fieldName>;
133 auto *thisExpr = checker->AllocNode<ir::ThisExpression>();
134 auto *argument = checker->AllocNode<ir::MemberExpression>(
135 thisExpr, fieldId->Clone(allocator, nullptr), ir::MemberExpressionKind::PROPERTY_ACCESS, false, false);
136
137 auto *statement = checker->AllocNode<ir::ReturnStatement>(argument);
138 statements->push_back(statement);
139
140 // Getter return type
141 *returnType = retType;
142 }
143 };
144
145 ir::ModifierFlags modifierFlags = ir::ModifierFlags::PUBLIC;
146 modifierFlags |= isSetter ? ir::ModifierFlags::SETTER : ir::ModifierFlags::GETTER;
147 ir::ScriptFunctionFlags funcFlags = ir::ScriptFunctionFlags::METHOD;
148 funcFlags |= isSetter ? ir::ScriptFunctionFlags::SETTER : ir::ScriptFunctionFlags::GETTER;
149
150 return checker->CreateClassMethod(ifaceMethod->Id()->Name().Utf8(), funcFlags, modifierFlags, methodBuilder);
151 }
152
FillClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody, const ArenaVector<ir::AstNode *> &ifaceBody, ir::ObjectExpression *objExpr)153 static void FillClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody,
154 const ArenaVector<ir::AstNode *> &ifaceBody, ir::ObjectExpression *objExpr)
155 {
156 for (auto *it : ifaceBody) {
157 ASSERT(it->IsMethodDefinition());
158 auto *ifaceMethod = it->AsMethodDefinition();
159
160 if (!ifaceMethod->Function()->IsGetter() && !ifaceMethod->Function()->IsSetter()) {
161 checker->ThrowTypeError("Interface has methods", objExpr->Start());
162 }
163
164 if (!ifaceMethod->Function()->IsGetter()) {
165 continue;
166 }
167
168 auto *field = CreateAnonClassField(ifaceMethod, checker);
169 classBody->push_back(field);
170
171 auto *getter = CreateAnonClassFieldGetterSetter(checker, ifaceMethod, false);
172 classBody->push_back(getter);
173
174 if (ifaceMethod->Overloads().size() == 1 && ifaceMethod->Overloads()[0]->Function()->IsSetter()) {
175 auto *setter = CreateAnonClassFieldGetterSetter(checker, ifaceMethod, true);
176 classBody->push_back(setter);
177 }
178 }
179 }
180
FillAnonClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody, ir::TSInterfaceDeclaration *ifaceNode, ir::ObjectExpression *objExpr)181 static void FillAnonClassBody(checker::ETSChecker *checker, ArenaVector<ir::AstNode *> *classBody,
182 ir::TSInterfaceDeclaration *ifaceNode, ir::ObjectExpression *objExpr)
183 {
184 for (auto *extendedIface : ifaceNode->TsType()->AsETSObjectType()->Interfaces()) {
185 auto extendedIfaceBody = extendedIface->GetDeclNode()->AsTSInterfaceDeclaration()->Body()->Body();
186 FillClassBody(checker, classBody, extendedIfaceBody, objExpr);
187 }
188
189 FillClassBody(checker, classBody, ifaceNode->Body()->Body(), objExpr);
190 }
191
GenerateAnonClassTypeFromInterface(checker::ETSChecker *checker, ir::TSInterfaceDeclaration *ifaceNode, ir::ObjectExpression *objExpr)192 static checker::ETSObjectType *GenerateAnonClassTypeFromInterface(checker::ETSChecker *checker,
193 ir::TSInterfaceDeclaration *ifaceNode,
194 ir::ObjectExpression *objExpr)
195 {
196 if (ifaceNode->GetAnonClass() != nullptr) {
197 return ifaceNode->GetAnonClass()->Definition()->TsType()->AsETSObjectType();
198 }
199
200 auto classBodyBuilder = [checker, ifaceNode, objExpr](ArenaVector<ir::AstNode *> *classBody) {
201 if (ifaceNode->TsType() == nullptr) {
202 ifaceNode->Check(checker);
203 }
204
205 FillAnonClassBody(checker, classBody, ifaceNode, objExpr);
206 classBody->push_back(CreateAnonClassImplCtor(checker));
207 };
208
209 util::UString className(util::StringView("$anonymous_class$"), checker->Allocator());
210 className.Append(ifaceNode->Id()->Name());
211 auto *classDecl = checker->BuildClass(className.View(), classBodyBuilder);
212 auto *classDef = classDecl->Definition();
213 auto *classType = classDef->TsType()->AsETSObjectType();
214
215 // Class type params
216 if (ifaceNode->TypeParams() != nullptr) {
217 // NOTE: to be done
218 checker->ThrowTypeError("Object literal cannot be of typed interface type", objExpr->Start());
219 }
220
221 // Class implements
222 auto *classImplements =
223 checker->AllocNode<ir::TSClassImplements>(checker->AllocNode<ir::OpaqueTypeNode>(ifaceNode->TsType()));
224 classImplements->SetParent(classDef);
225 classDef->Implements().emplace_back(classImplements);
226 classType->RemoveObjectFlag(checker::ETSObjectFlags::RESOLVED_INTERFACES);
227 checker->GetInterfacesOfClass(classType);
228
229 ifaceNode->SetAnonClass(classDecl);
230 return classType;
231 }
232
HandleInterfaceLowering(checker::ETSChecker *checker, ir::ObjectExpression *objExpr)233 static void HandleInterfaceLowering(checker::ETSChecker *checker, ir::ObjectExpression *objExpr)
234 {
235 const auto *const targetType = objExpr->TsType();
236 ASSERT(targetType->AsETSObjectType()->GetDeclNode()->IsTSInterfaceDeclaration());
237 auto *ifaceNode = targetType->AsETSObjectType()->GetDeclNode()->AsTSInterfaceDeclaration();
238 auto *resultType = GenerateAnonClassTypeFromInterface(checker, ifaceNode, objExpr);
239
240 if (const auto *const parent = objExpr->Parent(); parent->IsArrayExpression()) {
241 for (auto *elem : parent->AsArrayExpression()->Elements()) {
242 if (!elem->IsObjectExpression()) {
243 continue;
244 }
245 // Adjusting ts types of other object literals in array
246 elem->AsObjectExpression()->SetTsType(resultType);
247 }
248 }
249 objExpr->SetTsType(resultType);
250 }
251
Perform(public_lib::Context *ctx, parser::Program *program)252 bool InterfaceObjectLiteralLowering::Perform(public_lib::Context *ctx, parser::Program *program)
253 {
254 for (auto &[_, extPrograms] : program->ExternalSources()) {
255 (void)_;
256 for (auto *extProg : extPrograms) {
257 Perform(ctx, extProg);
258 }
259 }
260
261 auto *checker = ctx->checker->AsETSChecker();
262
263 program->Ast()->IterateRecursivelyPostorder([checker](ir::AstNode *ast) -> void {
264 if (ast->IsObjectExpression() && IsInterfaceType(ast->AsObjectExpression()->TsType())) {
265 HandleInterfaceLowering(checker, ast->AsObjectExpression());
266 }
267 });
268
269 return true;
270 }
271
Postcondition(public_lib::Context *ctx, const parser::Program *program)272 bool InterfaceObjectLiteralLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program)
273 {
274 for (auto &[_, extPrograms] : program->ExternalSources()) {
275 (void)_;
276 for (auto *extProg : extPrograms) {
277 if (!Postcondition(ctx, extProg)) {
278 return false;
279 }
280 }
281 }
282
283 return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) -> bool {
284 return ast->IsObjectExpression() && IsInterfaceType(ast->AsObjectExpression()->TsType());
285 });
286 }
287 } // namespace ark::es2panda::compiler
288