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