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 "memberExpression.h"
17
18 #include "checker/TSchecker.h"
19 #include "checker/ets/castingContext.h"
20 #include "checker/types/ets/etsTupleType.h"
21 #include "compiler/core/ETSGen.h"
22 #include "compiler/core/pandagen.h"
23 #include "ir/astDump.h"
24 #include "ir/srcDump.h"
25
26 namespace ark::es2panda::ir {
MemberExpression([[maybe_unused]] Tag const tag, MemberExpression const &other, ArenaAllocator *allocator)27 MemberExpression::MemberExpression([[maybe_unused]] Tag const tag, MemberExpression const &other,
28 ArenaAllocator *allocator)
29 : MemberExpression(other)
30 {
31 object_ = other.object_ != nullptr ? other.object_->Clone(allocator, this)->AsExpression() : nullptr;
32 property_ = other.property_ != nullptr ? other.property_->Clone(allocator, this)->AsExpression() : nullptr;
33 }
34
35 bool MemberExpression::IsPrivateReference() const noexcept
36 {
37 return property_->IsIdentifier() && property_->AsIdentifier()->IsPrivateIdent();
38 }
39
TransformChildren(const NodeTransformer &cb, std::string_view const transformationName)40 void MemberExpression::TransformChildren(const NodeTransformer &cb, std::string_view const transformationName)
41 {
42 if (auto *transformedNode = cb(object_); object_ != transformedNode) {
43 object_->SetTransformedNode(transformationName, transformedNode);
44 object_ = transformedNode->AsExpression();
45 }
46
47 if (auto *transformedNode = cb(property_); property_ != transformedNode) {
48 property_->SetTransformedNode(transformationName, transformedNode);
49 property_ = transformedNode->AsExpression();
50 }
51 }
52
Iterate(const NodeTraverser &cb) const53 void MemberExpression::Iterate(const NodeTraverser &cb) const
54 {
55 cb(object_);
56 cb(property_);
57 }
58
Dump(ir::AstDumper *dumper) const59 void MemberExpression::Dump(ir::AstDumper *dumper) const
60 {
61 dumper->Add({{"type", "MemberExpression"},
62 {"object", object_},
63 {"property", property_},
64 {"computed", computed_},
65 {"optional", IsOptional()}});
66 }
67
Dump(ir::SrcDumper *dumper) const68 void MemberExpression::Dump(ir::SrcDumper *dumper) const
69 {
70 ASSERT(object_ != nullptr);
71 ASSERT(property_ != nullptr);
72
73 object_->Dump(dumper);
74 if (IsOptional()) {
75 dumper->Add("?");
76 if ((MemberExpressionKind::ELEMENT_ACCESS & kind_) != 0U) {
77 dumper->Add(".");
78 }
79 }
80 if ((MemberExpressionKind::ELEMENT_ACCESS & kind_) != 0U) {
81 dumper->Add("[");
82 property_->Dump(dumper);
83 dumper->Add("]");
84 } else {
85 dumper->Add(".");
86 property_->Dump(dumper);
87 }
88 if ((parent_ != nullptr) && (parent_->IsBlockStatement() || parent_->IsBlockExpression())) {
89 dumper->Add(";");
90 dumper->Endl();
91 }
92 }
93
LoadRhs(compiler::PandaGen *pg) const94 void MemberExpression::LoadRhs(compiler::PandaGen *pg) const
95 {
96 compiler::RegScope rs(pg);
97 bool isSuper = object_->IsSuperExpression();
98 compiler::Operand prop = pg->ToPropertyKey(property_, computed_, isSuper);
99
100 if (isSuper) {
101 pg->LoadSuperProperty(this, prop);
102 } else if (IsPrivateReference()) {
103 const auto &name = property_->AsIdentifier()->Name();
104 compiler::VReg objReg = pg->AllocReg();
105 pg->StoreAccumulator(this, objReg);
106 compiler::VReg ctor = pg->AllocReg();
107 compiler::Function::LoadClassContexts(this, pg, ctor, name);
108 pg->ClassPrivateFieldGet(this, ctor, objReg, name);
109 } else {
110 pg->LoadObjProperty(this, prop);
111 }
112 }
113
CompileToRegs(compiler::PandaGen *pg, compiler::VReg object, compiler::VReg property) const114 void MemberExpression::CompileToRegs(compiler::PandaGen *pg, compiler::VReg object, compiler::VReg property) const
115 {
116 object_->Compile(pg);
117 pg->StoreAccumulator(this, object);
118
119 pg->OptionalChainCheck(IsOptional(), object);
120
121 if (!computed_) {
122 pg->LoadAccumulatorString(this, property_->AsIdentifier()->Name());
123 } else {
124 property_->Compile(pg);
125 }
126
127 pg->StoreAccumulator(this, property);
128 }
129
Compile(compiler::PandaGen *pg) const130 void MemberExpression::Compile(compiler::PandaGen *pg) const
131 {
132 pg->GetAstCompiler()->Compile(this);
133 }
134
CompileToReg(compiler::PandaGen *pg, compiler::VReg objReg) const135 void MemberExpression::CompileToReg(compiler::PandaGen *pg, compiler::VReg objReg) const
136 {
137 object_->Compile(pg);
138 pg->StoreAccumulator(this, objReg);
139 pg->OptionalChainCheck(IsOptional(), objReg);
140 LoadRhs(pg);
141 }
142
Compile(compiler::ETSGen *etsg) const143 void MemberExpression::Compile(compiler::ETSGen *etsg) const
144 {
145 etsg->GetAstCompiler()->Compile(this);
146 }
147
Check(checker::TSChecker *checker)148 checker::Type *MemberExpression::Check(checker::TSChecker *checker)
149 {
150 return checker->GetAnalyzer()->Check(this);
151 }
152
ResolveEnumMember(checker::ETSChecker *checker, checker::Type *type) const153 std::pair<checker::Type *, varbinder::LocalVariable *> MemberExpression::ResolveEnumMember(checker::ETSChecker *checker,
154 checker::Type *type) const
155 {
156 auto const *const enumInterface = [type]() -> checker::ETSEnumType const * {
157 if (type->IsETSIntEnumType()) {
158 return type->AsETSIntEnumType();
159 }
160 return type->AsETSStringEnumType();
161 }();
162
163 if (parent_->Type() == ir::AstNodeType::CALL_EXPRESSION && parent_->AsCallExpression()->Callee() == this) {
164 return {enumInterface->LookupMethod(checker, object_, property_->AsIdentifier()), nullptr};
165 }
166
167 auto *const literalType = enumInterface->LookupConstant(checker, object_, property_->AsIdentifier());
168 if (literalType == nullptr) {
169 return {nullptr, nullptr};
170 }
171 return {literalType, literalType->GetMemberVar()};
172 }
173
ResolveObjectMember( checker::ETSChecker *checker) const174 std::pair<checker::Type *, varbinder::LocalVariable *> MemberExpression::ResolveObjectMember(
175 checker::ETSChecker *checker) const
176 {
177 auto resolveRes = checker->ResolveMemberReference(this, objType_);
178 switch (resolveRes.size()) {
179 case 0U: {
180 /* resolution failed, error already reported */
181 return {nullptr, nullptr};
182 }
183 case 1U: {
184 if (resolveRes[0]->Kind() == checker::ResolvedKind::PROPERTY) {
185 auto var = resolveRes[0]->Variable()->AsLocalVariable();
186 checker->ValidatePropertyAccess(var, objType_, property_->Start());
187 return {checker->GetTypeOfVariable(var), var};
188 }
189 return {checker->GetTypeOfVariable(resolveRes[0]->Variable()), nullptr};
190 }
191 case 2U: {
192 auto classMethodType = checker->GetTypeOfVariable(resolveRes[1]->Variable());
193 auto extensionMethodType = checker->GetTypeOfVariable(resolveRes[0]->Variable());
194 auto *resolvedType = extensionMethodType;
195 if (classMethodType->IsETSFunctionType()) {
196 ASSERT(extensionMethodType->IsETSFunctionType());
197 resolvedType = checker->CreateETSExtensionFuncHelperType(classMethodType->AsETSFunctionType(),
198 extensionMethodType->AsETSFunctionType());
199 }
200 return {resolvedType, nullptr};
201 }
202 default: {
203 UNREACHABLE();
204 }
205 }
206 }
207
TraverseUnionMember(checker::ETSChecker *checker, checker::ETSUnionType *unionType, checker::Type *commonPropType)208 checker::Type *MemberExpression::TraverseUnionMember(checker::ETSChecker *checker, checker::ETSUnionType *unionType,
209 checker::Type *commonPropType)
210
211 {
212 auto const addPropType = [this, checker, &commonPropType](checker::Type *memberType) {
213 if (commonPropType != nullptr && commonPropType != memberType) {
214 checker->LogTypeError("Member type must be the same for all union objects.", Start());
215 } else {
216 commonPropType = memberType;
217 }
218 };
219 for (auto *const type : unionType->ConstituentTypes()) {
220 auto *const apparent = checker->GetApparentType(type);
221 if (apparent->IsETSObjectType()) {
222 SetObjectType(apparent->AsETSObjectType());
223 addPropType(ResolveObjectMember(checker).first);
224 } else if (apparent->IsETSEnumType()) {
225 addPropType(ResolveEnumMember(checker, apparent).first);
226 } else {
227 checker->LogTypeError({"Type ", unionType, " is illegal in union member expression."}, Start());
228 }
229 }
230 return commonPropType;
231 }
232
CheckUnionMember(checker::ETSChecker *checker, checker::Type *baseType)233 checker::Type *MemberExpression::CheckUnionMember(checker::ETSChecker *checker, checker::Type *baseType)
234 {
235 auto *const unionType = baseType->AsETSUnionType();
236 auto *const commonPropType = TraverseUnionMember(checker, unionType, nullptr);
237 SetObjectType(checker->GlobalETSObjectType());
238 return commonPropType;
239 }
240
AdjustType(checker::ETSChecker *checker, checker::Type *type)241 checker::Type *MemberExpression::AdjustType(checker::ETSChecker *checker, checker::Type *type)
242 {
243 auto *const objType = checker->GetApparentType(Object()->TsType());
244 if (PropVar() != nullptr) { // access erased property type
245 uncheckedType_ = checker->GuaranteedTypeForUncheckedPropertyAccess(PropVar());
246 } else if (IsComputed() && objType->IsETSArrayType()) { // access erased array or tuple type
247 uncheckedType_ = checker->GuaranteedTypeForUncheckedCast(objType->AsETSArrayType()->ElementType(), type);
248 }
249 SetTsType(type == nullptr ? checker->GlobalTypeError() : type);
250 return TsTypeOrError();
251 }
252
SetAndAdjustType(checker::ETSChecker *checker, checker::ETSObjectType *objectType)253 checker::Type *MemberExpression::SetAndAdjustType(checker::ETSChecker *checker, checker::ETSObjectType *objectType)
254 {
255 SetObjectType(objectType);
256 auto [resType, resVar] = ResolveObjectMember(checker);
257 if (resType == nullptr) {
258 SetTsType(checker->GlobalTypeError());
259 return checker->GlobalTypeError();
260 }
261 SetPropVar(resVar);
262 return AdjustType(checker, resType);
263 }
264
CheckArrayIndexValue(checker::ETSChecker *checker) const265 bool MemberExpression::CheckArrayIndexValue(checker::ETSChecker *checker) const
266 {
267 std::size_t index;
268
269 auto const &number = property_->AsNumberLiteral()->Number();
270
271 if (number.IsInteger()) {
272 auto const value = number.GetLong();
273 if (value < 0) {
274 checker->LogTypeError("Index value cannot be less than zero.", property_->Start());
275 return false;
276 }
277 index = static_cast<std::size_t>(value);
278 } else if (number.IsReal()) {
279 double value = number.GetDouble();
280 double fraction = std::modf(value, &value);
281 if (value < 0.0 || fraction >= std::numeric_limits<double>::epsilon()) {
282 checker->LogTypeError("Index value cannot be less than zero or fractional.", property_->Start());
283 return false;
284 }
285 index = static_cast<std::size_t>(value);
286 } else {
287 UNREACHABLE();
288 }
289
290 if (object_->IsArrayExpression() && object_->AsArrayExpression()->Elements().size() <= index) {
291 checker->LogTypeError("Index value cannot be greater than or equal to the array size.", property_->Start());
292 return false;
293 }
294
295 return true;
296 }
297
CheckIndexAccessMethod(checker::ETSChecker *checker)298 checker::Type *MemberExpression::CheckIndexAccessMethod(checker::ETSChecker *checker)
299 {
300 checker::PropertySearchFlags searchFlag =
301 checker::PropertySearchFlags::SEARCH_METHOD | checker::PropertySearchFlags::IS_FUNCTIONAL;
302 searchFlag |= checker::PropertySearchFlags::SEARCH_IN_BASE | checker::PropertySearchFlags::SEARCH_IN_INTERFACES;
303 // NOTE(DZ) maybe we need to exclude static methods: search_flag &= ~(checker::PropertySearchFlags::SEARCH_STATIC);
304
305 if (objType_->HasTypeFlag(checker::TypeFlag::GENERIC)) {
306 searchFlag |= checker::PropertySearchFlags::SEARCH_ALL;
307 }
308
309 bool const isSetter = Parent()->IsAssignmentExpression() && Parent()->AsAssignmentExpression()->Left() == this;
310 std::string_view const methodName =
311 isSetter ? compiler::Signatures::SET_INDEX_METHOD : compiler::Signatures::GET_INDEX_METHOD;
312
313 auto *const method = objType_->GetProperty(methodName, searchFlag);
314 if (method == nullptr || !method->HasFlag(varbinder::VariableFlags::METHOD)) {
315 checker->LogTypeError("Object type doesn't have proper index access method.", Start());
316 return nullptr;
317 }
318
319 ArenaVector<Expression *> arguments {checker->Allocator()->Adapter()};
320 arguments.emplace_back(property_);
321 if (isSetter) {
322 // Temporary change the parent of right assignment node to check if correct "$_set" function presents.
323 // later on in lowering the entire assignment expression will be replace top the call to that method.
324 auto *value = Parent()->AsAssignmentExpression()->Right();
325 value->SetParent(this);
326 arguments.emplace_back(value);
327 }
328
329 auto &signatures = checker->GetTypeOfVariable(method)->AsETSFunctionType()->CallSignatures();
330
331 checker::Signature *signature = checker->ValidateSignatures(signatures, nullptr, arguments, Start(), "indexing",
332 checker::TypeRelationFlag::NO_THROW);
333 if (signature == nullptr) {
334 checker->LogTypeError("Cannot find index access method with the required signature.", Property()->Start());
335 return nullptr;
336 }
337 checker->ValidateSignatureAccessibility(objType_, nullptr, signature, Start(),
338 "Index access method is not visible here.");
339
340 ASSERT(signature->Function() != nullptr);
341
342 if (signature->Function()->IsThrowing() || signature->Function()->IsRethrowing()) {
343 checker->CheckThrowingStatements(this);
344 }
345
346 if (isSetter) {
347 // Restore the right assignment node's parent to keep AST invariant valid.
348 Parent()->AsAssignmentExpression()->Right()->SetParent(Parent());
349 return signature->Params()[1]->TsType();
350 }
351
352 return signature->ReturnType();
353 }
354
CheckTupleAccessMethod(checker::ETSChecker *checker, checker::Type *baseType)355 checker::Type *MemberExpression::CheckTupleAccessMethod(checker::ETSChecker *checker, checker::Type *baseType)
356 {
357 ASSERT(baseType->IsETSTupleType());
358
359 auto idxIfAny = checker->GetTupleElementAccessValue(Property()->TsType(), Property()->Start());
360 if (!idxIfAny.has_value()) {
361 return nullptr;
362 }
363 auto *const tupleTypeAtIdx = baseType->AsETSTupleType()->GetTypeAtIndex(*idxIfAny);
364
365 if ((!Parent()->IsAssignmentExpression() || Parent()->AsAssignmentExpression()->Left() != this) &&
366 (!Parent()->IsUpdateExpression())) {
367 // Error never should be thrown by this call, because LUB of types can be converted to any type which
368 // LUB was calculated by casting
369 const checker::CastingContext cast(
370 checker->Relation(), {"Tuple type couldn't be converted "},
371 checker::CastingContext::ConstructorData {this, baseType->AsETSArrayType()->ElementType(), tupleTypeAtIdx,
372 Start()});
373 }
374
375 return tupleTypeAtIdx;
376 }
377
CheckComputed(checker::ETSChecker *checker, checker::Type *baseType)378 checker::Type *MemberExpression::CheckComputed(checker::ETSChecker *checker, checker::Type *baseType)
379 {
380 if (baseType->IsETSDynamicType()) {
381 checker->ValidateArrayIndex(property_);
382 return checker->GlobalBuiltinDynamicType(baseType->AsETSDynamicType()->Language());
383 }
384
385 if (baseType->IsETSArrayType()) {
386 auto *dflt = baseType->AsETSArrayType()->ElementType();
387 if (!baseType->IsETSTupleType() && !checker->ValidateArrayIndex(property_)) {
388 // error already reported to log
389 return dflt;
390 }
391
392 if (baseType->IsETSTupleType() && !checker->ValidateTupleIndex(baseType->AsETSTupleType(), this)) {
393 // error reported to log
394 return dflt;
395 }
396
397 // Check if the index value is inside array bounds if it is defined explicitly
398 if (property_->IsNumberLiteral() && !CheckArrayIndexValue(checker)) {
399 // error reported to log
400 return dflt;
401 }
402
403 // NOTE: apply capture conversion on this type
404 if (baseType->IsETSTupleType()) {
405 auto *res = CheckTupleAccessMethod(checker, baseType);
406 return (res == nullptr) ? dflt : res;
407 }
408
409 return dflt;
410 }
411
412 if (baseType->IsETSObjectType()) {
413 SetObjectType(baseType->AsETSObjectType());
414 return CheckIndexAccessMethod(checker);
415 }
416 if ((baseType->IsETSEnumType()) && (kind_ == MemberExpressionKind::ELEMENT_ACCESS)) {
417 property_->Check(checker);
418 if (property_->TsType()->IsETSEnumType()) {
419 AddAstNodeFlags(ir::AstNodeFlags::GENERATE_GET_NAME);
420 return checker->GlobalBuiltinETSStringType();
421 }
422 }
423 checker->LogTypeError("Indexed access is not supported for such expression type.", Object()->Start());
424 return nullptr;
425 }
426
Check(checker::ETSChecker *checker)427 checker::Type *MemberExpression::Check(checker::ETSChecker *checker)
428 {
429 return checker->GetAnalyzer()->Check(this);
430 }
431
Clone(ArenaAllocator *const allocator, AstNode *const parent)432 MemberExpression *MemberExpression::Clone(ArenaAllocator *const allocator, AstNode *const parent)
433 {
434 if (auto *const clone = allocator->New<MemberExpression>(Tag {}, *this, allocator); clone != nullptr) {
435 if (parent != nullptr) {
436 clone->SetParent(parent);
437 }
438
439 clone->SetRange(Range());
440 return clone;
441 }
442
443 throw Error(ErrorType::GENERIC, "", CLONE_ALLOCATION_ERROR);
444 }
445
446 } // namespace ark::es2panda::ir
447