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
22namespace ark::es2panda::compiler {
23
24std::string_view InterfaceObjectLiteralLowering::Name() const
25{
26    return "InterfaceObjectLiteralLowering";
27}
28
29static 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
36static 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
47static 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
82static 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
153static 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
181static 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
192static 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
233static 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
252bool 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
272bool 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