1/**
2 * Copyright (c) 2023-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 "interfacePropertyDeclarations.h"
17
18#include "checker/ETSchecker.h"
19#include "checker/types/type.h"
20#include "compiler/lowering/util.h"
21#include "ir/astNode.h"
22#include "ir/expression.h"
23#include "ir/expressions/identifier.h"
24#include "ir/opaqueTypeNode.h"
25#include "ir/statements/blockStatement.h"
26#include "ir/ts/tsInterfaceBody.h"
27#include "ir/base/classProperty.h"
28#include "ir/ets/etsUnionType.h"
29#include "ir/ets/etsNullishTypes.h"
30
31namespace ark::es2panda::compiler {
32
33namespace {
34
35void TransformOptionalFieldTypeAnnotation(checker::ETSChecker *const checker, ir::ClassProperty *const field)
36{
37    if (!field->IsOptionalDeclaration()) {
38        return;
39    }
40
41    if (field->IsETSUnionType()) {
42        bool alreadyHasUndefined = false;
43        auto unionTypes = field->AsETSUnionType()->Types();
44        for (const auto &type : unionTypes) {
45            if (type->IsETSUndefinedType()) {
46                alreadyHasUndefined = true;
47                break;
48            }
49        }
50        if (!alreadyHasUndefined) {
51            ArenaVector<ir::TypeNode *> types(field->AsETSUnionType()->Types(), checker->Allocator()->Adapter());
52            types.push_back(checker->AllocNode<ir::ETSUndefinedType>());
53            auto *const unionType = checker->AllocNode<ir::ETSUnionType>(std::move(types));
54            field->SetTypeAnnotation(unionType);
55        }
56    } else {
57        ArenaVector<ir::TypeNode *> types(checker->Allocator()->Adapter());
58        types.push_back(field->TypeAnnotation());
59        types.push_back(checker->AllocNode<ir::ETSUndefinedType>());
60        auto *const unionType = checker->AllocNode<ir::ETSUnionType>(std::move(types));
61        field->SetTypeAnnotation(unionType);
62    }
63    field->ClearModifier(ir::ModifierFlags::OPTIONAL);
64}
65
66}  // namespace
67
68static ir::MethodDefinition *GenerateGetterOrSetter(checker::ETSChecker *const checker, ir::ClassProperty *const field,
69                                                    bool isSetter)
70{
71    auto classScope = NearestScope(field);
72    auto *paramScope = checker->Allocator()->New<varbinder::FunctionParamScope>(checker->Allocator(), classScope);
73    auto *functionScope = checker->Allocator()->New<varbinder::FunctionScope>(checker->Allocator(), paramScope);
74
75    functionScope->BindParamScope(paramScope);
76    paramScope->BindFunctionScope(functionScope);
77
78    auto flags = ir::ModifierFlags::PUBLIC;
79    flags |= ir::ModifierFlags::ABSTRACT;
80
81    TransformOptionalFieldTypeAnnotation(checker, field);
82    ArenaVector<ir::Expression *> params(checker->Allocator()->Adapter());
83
84    if (isSetter) {
85        auto paramIdent = field->Key()->AsIdentifier()->Clone(checker->Allocator(), nullptr);
86        paramIdent->SetTsTypeAnnotation(field->TypeAnnotation()->Clone(checker->Allocator(), nullptr));
87        paramIdent->TypeAnnotation()->SetParent(paramIdent);
88
89        auto *const paramExpression = checker->AllocNode<ir::ETSParameterExpression>(paramIdent, nullptr);
90        paramExpression->SetRange(paramIdent->Range());
91        auto *const paramVar = std::get<2>(paramScope->AddParamDecl(checker->Allocator(), paramExpression));
92
93        paramIdent->SetVariable(paramVar);
94        paramExpression->SetVariable(paramVar);
95
96        params.push_back(paramExpression);
97    }
98
99    auto signature = ir::FunctionSignature(nullptr, std::move(params), isSetter ? nullptr : field->TypeAnnotation());
100
101    auto *func = checker->AllocNode<ir::ScriptFunction>(
102        checker->Allocator(), ir::ScriptFunction::ScriptFunctionData {nullptr, std::move(signature),
103                                                                      isSetter ? ir::ScriptFunctionFlags::SETTER
104                                                                               : ir::ScriptFunctionFlags::GETTER,
105                                                                      flags, true});
106
107    func->SetRange(field->Range());
108
109    func->SetScope(functionScope);
110
111    auto const &name = field->Key()->AsIdentifier()->Name();
112    auto methodIdent = checker->AllocNode<ir::Identifier>(name, checker->Allocator());
113    auto *decl = checker->Allocator()->New<varbinder::VarDecl>(name);
114    auto var = functionScope->AddDecl(checker->Allocator(), decl, ScriptExtension::ETS);
115
116    methodIdent->SetVariable(var);
117
118    auto *funcExpr = checker->AllocNode<ir::FunctionExpression>(func);
119    funcExpr->SetRange(func->Range());
120    func->AddFlag(ir::ScriptFunctionFlags::METHOD);
121
122    auto *method = checker->AllocNode<ir::MethodDefinition>(ir::MethodDefinitionKind::METHOD, methodIdent, funcExpr,
123                                                            flags, checker->Allocator(), false);
124
125    method->Id()->SetMutator();
126    method->SetRange(field->Range());
127    method->Function()->SetIdent(method->Id()->Clone(checker->Allocator(), nullptr));
128    method->Function()->AddModifier(method->Modifiers());
129    paramScope->BindNode(func);
130    functionScope->BindNode(func);
131
132    return method;
133}
134
135static ir::Expression *UpdateInterfacePropertys(checker::ETSChecker *const checker,
136                                                ir::TSInterfaceBody *const interface)
137{
138    if (interface->Body().empty()) {
139        return interface;
140    }
141
142    auto propertyList = interface->Body();
143    ArenaVector<ir::AstNode *> newPropertyList(checker->Allocator()->Adapter());
144
145    auto scope = NearestScope(interface);
146    ASSERT(scope->IsClassScope());
147
148    for (const auto &prop : propertyList) {
149        if (!prop->IsClassProperty()) {
150            newPropertyList.emplace_back(prop);
151            continue;
152        }
153        auto getter = GenerateGetterOrSetter(checker, prop->AsClassProperty(), false);
154        newPropertyList.emplace_back(getter);
155
156        auto methodScope = scope->AsClassScope()->InstanceMethodScope();
157        auto name = getter->Key()->AsIdentifier()->Name();
158
159        auto *decl = checker->Allocator()->New<varbinder::FunctionDecl>(checker->Allocator(), name, getter);
160
161        if (methodScope->AddDecl(checker->Allocator(), decl, ScriptExtension::ETS) == nullptr) {
162            auto prevDecl = methodScope->FindDecl(name);
163            ASSERT(prevDecl->IsFunctionDecl());
164            prevDecl->Node()->AsMethodDefinition()->AddOverload(getter);
165
166            if (!prop->AsClassProperty()->IsReadonly()) {
167                auto setter = GenerateGetterOrSetter(checker, prop->AsClassProperty(), true);
168                newPropertyList.emplace_back(setter);
169                prevDecl->Node()->AsMethodDefinition()->AddOverload(setter);
170            }
171
172            getter->Function()->Id()->SetVariable(
173                methodScope->FindLocal(name, varbinder::ResolveBindingOptions::BINDINGS));
174            continue;
175        }
176
177        if (!prop->AsClassProperty()->IsReadonly()) {
178            auto setter = GenerateGetterOrSetter(checker, prop->AsClassProperty(), true);
179            newPropertyList.emplace_back(setter);
180            getter->AddOverload(setter);
181        }
182        scope->AsClassScope()->InstanceFieldScope()->EraseBinding(name);
183    }
184
185    auto newInterface = checker->AllocNode<ir::TSInterfaceBody>(std::move(newPropertyList));
186    newInterface->SetRange(interface->Range());
187    newInterface->SetParent(interface->Parent());
188
189    return newInterface;
190}
191
192bool InterfacePropertyDeclarationsPhase::Perform(public_lib::Context *ctx, parser::Program *program)
193{
194    for (const auto &[_, ext_programs] : program->ExternalSources()) {
195        (void)_;
196        for (auto *const extProg : ext_programs) {
197            Perform(ctx, extProg);
198        }
199    }
200
201    checker::ETSChecker *const checker = ctx->checker->AsETSChecker();
202
203    program->Ast()->TransformChildrenRecursively(
204        [checker](ir::AstNode *const ast) -> ir::AstNode * {
205            return ast->IsTSInterfaceBody() ? UpdateInterfacePropertys(checker, ast->AsTSInterfaceBody()) : ast;
206        },
207        Name());
208
209    return true;
210}
211
212}  // namespace ark::es2panda::compiler
213