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