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 
31 namespace ark::es2panda::testing {
32 
33 class DynamicCall : public Es2pandaUnitGtest {
34 public:
ParseExpr(const std::string &strExpr)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 
MarkChainDynamic(ir::Expression *obj)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 
ParseDynExpr(const std::string &strExpr)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 
AddDynImport(const char *specifierName, varbinder::ETSBinder *varbinder, ir::Identifier *node)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 
AssertNameEq(const ArenaVector<util::StringView> &name, std::initializer_list<const char *> expected)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 
TEST_F(DynamicCall, JoinDynMemberChain)111 TEST_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 
TEST_F(DynamicCall, JoinCompitedMemberChain)130 TEST_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 
TEST_F(DynamicCall, JoinDynCallMember)151 TEST_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 
TEST_F(DynamicCall, JoinDynStaticCallMember)164 TEST_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 
TEST_F(DynamicCall, TsQualifiedName)184 TEST_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