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
26namespace ark::es2panda::ir {
27MemberExpression::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
35bool MemberExpression::IsPrivateReference() const noexcept
36{
37    return property_->IsIdentifier() && property_->AsIdentifier()->IsPrivateIdent();
38}
39
40void 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
53void MemberExpression::Iterate(const NodeTraverser &cb) const
54{
55    cb(object_);
56    cb(property_);
57}
58
59void 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
68void 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
94void 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
114void 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
130void MemberExpression::Compile(compiler::PandaGen *pg) const
131{
132    pg->GetAstCompiler()->Compile(this);
133}
134
135void 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
143void MemberExpression::Compile(compiler::ETSGen *etsg) const
144{
145    etsg->GetAstCompiler()->Compile(this);
146}
147
148checker::Type *MemberExpression::Check(checker::TSChecker *checker)
149{
150    return checker->GetAnalyzer()->Check(this);
151}
152
153std::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
174std::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
208checker::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
233checker::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
241checker::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
253checker::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
265bool 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
298checker::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
355checker::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
378checker::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
427checker::Type *MemberExpression::Check(checker::ETSChecker *checker)
428{
429    return checker->GetAnalyzer()->Check(this);
430}
431
432MemberExpression *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