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 "forOfStatement.h"
17
18#include "checker/TSchecker.h"
19#include "compiler/core/pandagen.h"
20#include "compiler/core/ETSGen.h"
21#include "ir/astDump.h"
22#include "ir/srcDump.h"
23
24namespace ark::es2panda::ir {
25
26checker::Type *ForOfStatement::CreateUnionIteratorTypes(checker::ETSChecker *checker, checker::Type *exprType)
27{
28    ArenaVector<checker::Type *> types(checker->Allocator()->Adapter());
29
30    for (auto it : exprType->AsETSUnionType()->ConstituentTypes()) {
31        if (it->IsETSStringType()) {
32            types.push_back(checker->GetGlobalTypesHolder()->GlobalCharType());
33        } else if (it->IsETSObjectType()) {
34            types.push_back(this->CheckIteratorMethodForObject(checker, it->AsETSObjectType()));
35        } else if (it->IsETSArrayType()) {
36            types.push_back(it->AsETSArrayType()->ElementType()->Instantiate(checker->Allocator(), checker->Relation(),
37                                                                             checker->GetGlobalTypesHolder()));
38            types.back()->RemoveTypeFlag(checker::TypeFlag::CONSTANT);
39        } else {
40            return nullptr;
41        }
42    }
43
44    return checker->CreateETSUnionType(std::move(types));
45}
46
47void ForOfStatement::TransformChildren(const NodeTransformer &cb, std::string_view transformationName)
48{
49    if (auto *transformedNode = cb(left_); left_ != transformedNode) {
50        left_->SetTransformedNode(transformationName, transformedNode);
51        left_ = transformedNode;
52    }
53
54    if (auto *transformedNode = cb(right_); right_ != transformedNode) {
55        right_->SetTransformedNode(transformationName, transformedNode);
56        right_ = transformedNode->AsExpression();
57    }
58
59    if (auto *transformedNode = cb(body_); body_ != transformedNode) {
60        body_->SetTransformedNode(transformationName, transformedNode);
61        body_ = transformedNode->AsStatement();
62    }
63}
64
65void ForOfStatement::Iterate(const NodeTraverser &cb) const
66{
67    cb(left_);
68    cb(right_);
69    cb(body_);
70}
71
72void ForOfStatement::Dump(ir::AstDumper *dumper) const
73{
74    dumper->Add({{"type", "ForOfStatement"}, {"await", isAwait_}, {"left", left_}, {"right", right_}, {"body", body_}});
75}
76
77void ForOfStatement::Dump(ir::SrcDumper *dumper) const
78{
79    ASSERT(left_ != nullptr);
80    ASSERT(right_ != nullptr);
81    dumper->Add("for ");
82    if (isAwait_) {
83        dumper->Add("await ");
84    }
85    dumper->Add("(");
86    left_->Dump(dumper);
87    dumper->Add(" of ");
88    right_->Dump(dumper);
89    dumper->Add(") {");
90    if (body_ != nullptr) {
91        dumper->IncrIndent();
92        dumper->Endl();
93        body_->Dump(dumper);
94        dumper->DecrIndent();
95        dumper->Endl();
96    }
97    dumper->Add("}");
98}
99
100void ForOfStatement::Compile(compiler::PandaGen *pg) const
101{
102    pg->GetAstCompiler()->Compile(this);
103}
104
105void ForOfStatement::Compile(compiler::ETSGen *etsg) const
106{
107    etsg->GetAstCompiler()->Compile(this);
108}
109
110checker::Type *ForOfStatement::Check(checker::TSChecker *checker)
111{
112    return checker->GetAnalyzer()->Check(this);
113}
114
115checker::Type *ForOfStatement::Check(checker::ETSChecker *checker)
116{
117    return checker->GetAnalyzer()->Check(this);
118}
119
120ForOfStatement *ForOfStatement::Clone(ArenaAllocator *const allocator, AstNode *const parent)
121{
122    auto *const left = left_ != nullptr ? left_->Clone(allocator, nullptr) : nullptr;
123    auto *const right = right_ != nullptr ? right_->Clone(allocator, nullptr)->AsExpression() : nullptr;
124    auto *const body = body_ != nullptr ? body_->Clone(allocator, nullptr)->AsStatement() : nullptr;
125
126    if (auto *const clone = allocator->New<ForOfStatement>(left, right, body, isAwait_); clone != nullptr) {
127        if (left != nullptr) {
128            left->SetParent(clone);
129        }
130
131        if (right != nullptr) {
132            right->SetParent(clone);
133        }
134
135        if (body != nullptr) {
136            body->SetParent(clone);
137        }
138
139        if (parent != nullptr) {
140            clone->SetParent(parent);
141        }
142
143        clone->SetRange(Range());
144        return clone;
145    }
146
147    throw Error(ErrorType::GENERIC, "", CLONE_ALLOCATION_ERROR);
148}
149
150checker::Type *ForOfStatement::CheckIteratorMethodForObject(checker::ETSChecker *checker,
151                                                            checker::ETSObjectType *sourceType)
152{
153    auto const &position = right_->Start();
154
155    checker::PropertySearchFlags searchFlag =
156        checker::PropertySearchFlags::SEARCH_METHOD | checker::PropertySearchFlags::IS_FUNCTIONAL;
157    searchFlag |= checker::PropertySearchFlags::SEARCH_IN_BASE | checker::PropertySearchFlags::SEARCH_IN_INTERFACES;
158    // NOTE: maybe we need to exclude static methods: search_flag &= ~(checker::PropertySearchFlags::SEARCH_STATIC)
159
160    if (sourceType->HasTypeFlag(checker::TypeFlag::GENERIC)) {
161        searchFlag |= checker::PropertySearchFlags::SEARCH_ALL;
162    }
163
164    auto *const method = sourceType->GetProperty(compiler::Signatures::ITERATOR_METHOD, searchFlag);
165    if (method == nullptr || !method->HasFlag(varbinder::VariableFlags::METHOD)) {
166        checker->LogTypeError("Object type doesn't have proper iterator method.", position);
167        return nullptr;
168    }
169
170    ArenaVector<Expression *> arguments {checker->Allocator()->Adapter()};
171    auto &signatures = checker->GetTypeOfVariable(method)->AsETSFunctionType()->CallSignatures();
172
173    checker::Signature *signature = checker->ValidateSignatures(signatures, nullptr, arguments, position, "iterator",
174                                                                checker::TypeRelationFlag::NO_THROW);
175    if (signature == nullptr) {
176        checker->LogTypeError("Cannot find iterator method with the required signature.", position);
177        return nullptr;
178    }
179    checker->ValidateSignatureAccessibility(sourceType, nullptr, signature, position,
180                                            "Iterator method is not visible here.");
181
182    ASSERT(signature->Function() != nullptr);
183
184    if (signature->Function()->IsThrowing() || signature->Function()->IsRethrowing()) {
185        checker->CheckThrowingStatements(this);
186    }
187
188    if (!CheckReturnTypeOfIteratorMethod(checker, sourceType, signature, position)) {
189        return nullptr;
190    }
191
192    auto *const nextMethod =
193        signature->ReturnType()->AsETSObjectType()->GetProperty(ITERATOR_INTERFACE_METHOD, searchFlag);
194    if (nextMethod == nullptr || !nextMethod->HasFlag(varbinder::VariableFlags::METHOD)) {
195        checker->LogTypeError("Iterator object doesn't have proper next method.", position);
196        return nullptr;
197    }
198
199    auto &nextSignatures = checker->GetTypeOfVariable(nextMethod)->AsETSFunctionType()->CallSignatures();
200
201    auto const *const nextSignature = checker->ValidateSignatures(nextSignatures, nullptr, arguments, position,
202                                                                  "iterator", checker::TypeRelationFlag::NO_THROW);
203    if (nextSignature != nullptr && nextSignature->ReturnType()->IsETSObjectType()) {
204        if (auto const *const resultType = nextSignature->ReturnType()->AsETSObjectType();
205            resultType->Name().Is(ITERATOR_RESULT_NAME)) {
206            return resultType->TypeArguments()[0];
207        }
208    }
209
210    return nullptr;
211}
212
213bool ForOfStatement::CheckReturnTypeOfIteratorMethod(checker::ETSChecker *checker, checker::ETSObjectType *sourceType,
214                                                     checker::Signature *signature,
215                                                     const lexer::SourcePosition &position)
216{
217    if ((signature->ReturnType() == nullptr || signature->ReturnType() == checker->GlobalVoidType()) &&
218        signature->Function()->HasBody() && signature->Function()->Body()->IsBlockStatement()) {
219        for (auto *const it : signature->Function()->Body()->AsBlockStatement()->Statements()) {
220            if (it->IsReturnStatement()) {
221                checker::SavedCheckerContext savedContext(checker, checker::CheckerStatus::IN_CLASS, sourceType);
222                it->AsReturnStatement()->Check(checker);
223                break;
224            }
225        }
226    }
227
228    if (signature->ReturnType() != nullptr && signature->ReturnType()->IsETSObjectType() &&
229        ForOfStatement::CheckIteratorInterfaceForObject(checker, signature->ReturnType()->AsETSObjectType())) {
230        return true;
231    }
232
233    checker->LogTypeError("Iterator method must return an object which implements Iterator<T>", position);
234    return false;
235}
236
237bool ForOfStatement::CheckIteratorInterfaceForObject(checker::ETSChecker *checker, checker::ETSObjectType *obj)
238{
239    for (auto *const it : obj->Interfaces()) {
240        if (it->Name().Is(ITERATOR_INTERFACE_NAME)) {
241            return true;
242        }
243    }
244
245    return obj->SuperType() != nullptr && obj->SuperType()->IsETSObjectType() &&
246           CheckIteratorInterfaceForObject(checker, obj->SuperType()->AsETSObjectType());
247}
248
249checker::Type *ForOfStatement::CheckIteratorMethod(checker::ETSChecker *const checker)
250{
251    if (auto *exprType = right_->TsType(); exprType != nullptr) {
252        if (exprType->IsETSObjectType()) {
253            return CheckIteratorMethodForObject(checker, exprType->AsETSObjectType());
254        }
255
256        if (exprType->IsETSUnionType()) {
257            return this->CreateUnionIteratorTypes(checker, exprType);
258        }
259    }
260
261    return nullptr;
262}
263}  // namespace ark::es2panda::ir
264