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