1/*
2 * Copyright (c) 2021 - 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 "checker/ets/dynamic/dynamicCall.h"
17#include "checker/types/ets/etsDynamicType.h"
18#include "test/unit/es2panda_unit_gtest.h"
19#include "ir/expressions/callExpression.h"
20#include "ir/expressions/memberExpression.h"
21#include "ir/expressions/identifier.h"
22#include "ir/ets/etsNewClassInstanceExpression.h"
23#include "ir/ets/etsTypeReferencePart.h"
24#include "ir/ets/etsTypeReference.h"
25#include "ir/ts/tsQualifiedName.h"
26#include "util/helpers.h"
27#include "compiler/lowering/scopesInit/scopesInitPhase.h"
28#include "util/language.h"
29#include "parser/ETSparser.h"
30
31namespace ark::es2panda::testing {
32
33class DynamicCall : public Es2pandaUnitGtest {
34public:
35    std::pair<parser::Program *, ir::Expression *> ParseExpr(const std::string &strExpr)
36    {
37        auto program =
38            Allocator()->New<parser::Program>(Allocator(), Allocator()->New<varbinder::ETSBinder>(Allocator()));
39        program->VarBinder()->SetProgram(program);
40        program->VarBinder()->InitTopScope();
41        auto etsParser = parser::ETSParser(program, CompilerOptions {});
42        auto expr = etsParser.CreateExpression(strExpr);
43        return {program, expr};
44    }
45
46    ir::Expression *MarkChainDynamic(ir::Expression *obj)
47    {
48        if (obj == nullptr) {
49            return nullptr;
50        }
51        auto dynamicType = Allocator()->New<checker::ETSDynamicType>(
52            Allocator(), std::make_tuple("test", "test", Language::FromString("sts").value()),
53            std::make_tuple(obj, checker::ETSObjectFlags::NO_OPTS, nullptr), false);
54        if (obj->IsETSTypeReference()) {
55            obj = obj->AsETSTypeReference()->Part()->Name();
56        }
57        while (obj != nullptr && (obj->IsMemberExpression() || obj->IsTSQualifiedName())) {
58            obj->SetTsType(dynamicType);
59            if (obj->IsMemberExpression()) {
60                obj = obj->AsMemberExpression()->Object();
61            } else if (obj->IsTSQualifiedName()) {
62                obj = obj->AsTSQualifiedName()->Left();
63            }
64        }
65        obj->SetTsType(dynamicType);
66        return obj;
67    }
68
69    std::tuple<parser::Program *, ir::Expression *, ir::Expression *> ParseDynExpr(const std::string &strExpr)
70    {
71        auto [prog, expr] = ParseExpr(strExpr);
72        ir::Expression *obj = nullptr;
73        if (expr->IsCallExpression()) {
74            obj = expr->AsCallExpression()->Callee();
75        } else {
76            obj = expr->AsETSNewClassInstanceExpression()->GetTypeRef()->AsETSTypeReference();
77        }
78        auto first = MarkChainDynamic(obj);
79        return {prog, obj, first};
80    }
81
82    void AddDynImport(const char *specifierName, varbinder::ETSBinder *varbinder, ir::Identifier *node)
83    {
84        auto aIdent = Allocator()->New<ir::Identifier>(specifierName, Allocator());
85        ArenaVector<ir::AstNode *> specifiers {Allocator()->Adapter()};
86        auto specifier = Allocator()->New<ir::ImportSpecifier>(aIdent, aIdent);
87        specifiers.emplace_back(specifier);
88        auto importSrc = Allocator()->New<ir::ImportSource>(Allocator()->New<ir::StringLiteral>("/tmp"),
89                                                            Allocator()->New<ir::StringLiteral>(),
90                                                            Language::FromString("js").value(), false);
91        auto importDecl =
92            util::NodeAllocator::Alloc<ir::ETSImportDeclaration>(Allocator(), importSrc, std::move(specifiers));
93        compiler::InitScopesPhaseETS::RunExternalNode(importDecl, varbinder);
94        varbinder->BuildImportDeclaration(importDecl);
95        auto var = varbinder->TopScope()->Find(specifierName);
96        node->SetVariable(var.variable);
97    }
98
99    void AssertNameEq(const ArenaVector<util::StringView> &name, std::initializer_list<const char *> expected)
100    {
101        ASSERT_EQ(name.size(), expected.size());
102        auto it1 = expected.begin();
103        auto it2 = name.begin();
104        while (it2 != name.end()) {
105            ASSERT_EQ(util::StringView(*it1), *it2);
106            it1++, it2++;
107        }
108    }
109};
110
111TEST_F(DynamicCall, JoinDynMemberChain)
112{
113    auto strExpr = "A.b.c.d()";
114    auto [prog, obj, first] = ParseDynExpr(strExpr);
115    auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression());
116    AssertNameEq(name, {"b", "c", "d"});
117    ASSERT(squeezedObj->IsIdentifier());
118    auto varbinder = prog->VarBinder()->AsETSBinder();
119    {
120        // With empty varbinder A is local variable
121        auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
122        AssertNameEq(callName, {"b", "c", "d"});
123    }
124    // Now A is import => we can optimize
125    AddDynImport("A", varbinder, first->AsIdentifier());
126    auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
127    AssertNameEq(callName, {"A", "b", "c", "d"});
128}
129
130TEST_F(DynamicCall, JoinCompitedMemberChain)
131{
132    auto strExpr = "A.b.c[0].d.e.f()";
133    auto [prog, obj, first] = ParseDynExpr(strExpr);
134    auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression());
135    // Can't optimize []
136    AssertNameEq(name, {"d", "e", "f"});
137    ASSERT_EQ(squeezedObj,
138              obj->AsMemberExpression()->Object()->AsMemberExpression()->Object()->AsMemberExpression()->Object());
139    auto varbinder = prog->VarBinder()->AsETSBinder();
140    {
141        // Can't optimize []
142        auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
143        AssertNameEq(callName, {"d", "e", "f"});
144    }
145    // Can't optimize []
146    AddDynImport("A", varbinder, first->AsIdentifier());
147    auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
148    AssertNameEq(callName, {"d", "e", "f"});
149}
150
151TEST_F(DynamicCall, JoinDynCallMember)
152{
153    auto strExpr = "A.b().c.d()";
154    auto [program, obj, first] = ParseDynExpr(strExpr);
155    auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression());
156    AssertNameEq(name, {"c", "d"});
157    ASSERT_EQ(squeezedObj, obj->AsMemberExpression()->Object()->AsMemberExpression()->Object());
158
159    auto varbinder = program->VarBinder()->AsETSBinder();
160    auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
161    AssertNameEq(callName, {"c", "d"});
162}
163
164TEST_F(DynamicCall, JoinDynStaticCallMember)
165{
166    auto strExpr = "A.b.c.d.e()";
167    auto [program, obj, first] = ParseDynExpr(strExpr);
168
169    auto bObj = obj->AsMemberExpression()->Object()->AsMemberExpression()->Object();
170    ASSERT_EQ(bObj->AsMemberExpression()->Property()->AsIdentifier()->Name(), "c");
171    auto staticType = Allocator()->New<checker::ETSObjectType>(Allocator(), checker::ETSObjectFlags::NO_OPTS);
172    bObj->AsMemberExpression()->Object()->SetTsType(staticType);
173
174    auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression());
175    AssertNameEq(name, {"d", "e"});
176    ASSERT_EQ(squeezedObj, bObj);
177
178    auto varbinder = program->VarBinder()->AsETSBinder();
179    AddDynImport("A", varbinder, first->AsIdentifier());
180    auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
181    AssertNameEq(callName, {"d", "e"});
182}
183
184TEST_F(DynamicCall, TsQualifiedName)
185{
186    auto strExpr = "new A.b.c.d()";
187    auto [program, obj, first] = ParseDynExpr(strExpr);
188    auto varbinder = program->VarBinder()->AsETSBinder();
189    AddDynImport("A", varbinder, first->AsIdentifier());
190    auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj);
191    AssertNameEq(callName, {"A", "b", "c", "d"});
192}
193
194}  // namespace ark::es2panda::testing
195