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