/* * Copyright (c) 2021-2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // // desc: For-of-loop syntax is translated to the while-loop syntax by calling of special method // providing predefined 'iterator' interface: // for (let x of c) { // c is an object of 'iterable' class // // } // ... // let_ci_=_c.$_iterator() // let_it_=_ci.next() // while_(!it.done)_{ // x_=_it.value! // // it_=_ci.next() // } // #include "objectIterator.h" #include "generated/signatures.h" #include "macros.h" #include "parser/ETSparser.h" #include "compiler/lowering/util.h" #include "compiler/lowering/scopesInit/scopesInitPhase.h" #include "checker/ETSchecker.h" #include "util/options.h" namespace ark::es2panda::compiler { static constexpr std::size_t const WHILE_LOOP_POSITION = 1U; static constexpr std::size_t const WHILE_LOOP_SIZE = 3U; std::string_view ObjectIteratorLowering::Name() const { static std::string const NAME = "ObjectIteratorLowering"; return NAME; } void ObjectIteratorLowering::TransferForOfLoopBody(ir::Statement *const forBody, ir::BlockStatement *const whileBody) const noexcept { ES2PANDA_ASSERT(forBody != nullptr && whileBody != nullptr); auto &whileStatements = whileBody->Statements(); // Currently while loop body consists of 2 statements: 'x = it.value!' and 'it = ci.next()' // We need to insert the body of original for-of-loop between them, change their parent and // probably clean types for expressions and variables for identifier for subsequent re-check. if (forBody->IsBlockStatement()) { auto &forStatements = forBody->AsBlockStatement()->Statements(); std::size_t const forSize = forStatements.size(); whileStatements.resize(WHILE_LOOP_SIZE + forSize); for (std::size_t i = 0U; i < forSize; ++i) { auto &statement = forStatements[i]; statement->SetParent(whileBody); ClearTypesVariablesAndScopes(statement); whileStatements[WHILE_LOOP_SIZE + i] = statement; } } else { whileStatements.resize(WHILE_LOOP_SIZE + 1U); forBody->SetParent(whileBody); ClearTypesVariablesAndScopes(forBody); whileStatements[WHILE_LOOP_SIZE] = forBody; } } // interface Iterator maybe implements by other classes // we need the instantiated // so to find in interface and super checker::Type *FindInstantiatedTypeParamFromIterator(checker::ETSObjectType *itor) { if (itor == nullptr) { return nullptr; } if (itor->Name() == compiler::Signatures::ITERATOR_CLASS) { return itor->TypeArguments().front(); } for (auto interface : itor->Interfaces()) { if (auto type = FindInstantiatedTypeParamFromIterator(interface); type != nullptr) { return type; } } if (auto type = FindInstantiatedTypeParamFromIterator(itor->SuperType()); type != nullptr) { return type; } return nullptr; } static ir::OpaqueTypeNode *FindIterValueType(checker::ETSObjectType *type, ArenaAllocator *allocator) { auto *itor = type->GetProperty(compiler::Signatures::ITERATOR_METHOD, checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD | checker::PropertySearchFlags::SEARCH_IN_INTERFACES | checker::PropertySearchFlags::SEARCH_IN_BASE); ES2PANDA_ASSERT(itor != nullptr); auto const &sigs = itor->TsType()->AsETSFunctionType()->CallSignatures(); checker::ETSObjectType *itorReturnType = nullptr; for (auto &sig : sigs) { if (sig->Params().empty()) { itorReturnType = sig->ReturnType()->AsETSObjectType(); break; } } ES2PANDA_ASSERT(itorReturnType); auto *valueType = FindInstantiatedTypeParamFromIterator(itorReturnType); return allocator->New(valueType, allocator); } ir::Statement *ObjectIteratorLowering::ProcessObjectIterator(public_lib::Context *ctx, ir::ForOfStatement *forOfStatement) const { // Note! We assume that parser, varbinder and checker phases have been already passed correctly, thus the // class has required accessible iterator method and all the types and scopes are properly resolved. auto *const allocator = ctx->Allocator(); auto *const varbinder = ctx->checker->VarBinder()->AsETSBinder(); ES2PANDA_ASSERT(varbinder != nullptr); auto statementScope = varbinder::LexicalScope::Enter(varbinder, NearestScope(forOfStatement)); ir::Identifier *const iterIdent = Gensym(allocator); ir::Identifier *const nextIdent = Gensym(allocator); ir::Identifier *loopVariableIdent = nullptr; // find $_iterator->ReturnType->Iterator->number // we cannot simply use next().value! , because value itself maybe undefined or null ir::AstNode *typeNode; auto exprType = forOfStatement->Right()->TsType(); if (!exprType->IsETSObjectType()) { return forOfStatement; } typeNode = FindIterValueType(exprType->AsETSObjectType(), allocator); // Replace the for-of loop with the while loop using the provided iterator interface std::string whileStatement = "let @@I1 = (@@E2)." + std::string {compiler::Signatures::ITERATOR_METHOD} + "(); "; whileStatement += "while (true) { "; whileStatement += "let @@I3 = @@I4.next(); "; whileStatement += "if (@@I5.done) break;"; if (auto *const left = forOfStatement->Left(); left->IsVariableDeclaration()) { auto *const declaration = left->AsVariableDeclaration(); whileStatement += declaration->Kind() != ir::VariableDeclaration::VariableDeclarationKind::CONST ? "let " : "const "; loopVariableIdent = declaration->Declarators().at(0U)->Id()->AsIdentifier()->Clone(allocator, nullptr); } else if (left->IsIdentifier()) { loopVariableIdent = Gensym(allocator); loopVariableIdent->SetName(left->AsIdentifier()->Name()); } else { ES2PANDA_UNREACHABLE(); } whileStatement += "@@I6 = (@@I7.value as @@T8);}; "; // Parse ArkTS code string and create corresponding AST nodes auto *const parser = ctx->parser->AsETSParser(); ES2PANDA_ASSERT(parser != nullptr); auto *const loweringResult = parser->CreateFormattedStatement( whileStatement, iterIdent, forOfStatement->Right(), nextIdent, iterIdent->Clone(allocator, nullptr), nextIdent->Clone(allocator, nullptr), loopVariableIdent, nextIdent->Clone(allocator, nullptr), typeNode); ES2PANDA_ASSERT(loweringResult != nullptr); loweringResult->SetParent(forOfStatement->Parent()); loweringResult->SetRange(forOfStatement->Range()); TransferForOfLoopBody(forOfStatement->Body(), loweringResult->AsBlockStatement() ->Statements()[WHILE_LOOP_POSITION] ->AsWhileStatement() ->Body() ->AsBlockStatement()); auto *const checker = ctx->checker->AsETSChecker(); ES2PANDA_ASSERT(checker != nullptr); CheckLoweredNode(varbinder, checker, loweringResult); return loweringResult; } bool ObjectIteratorLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) { auto hasIterator = [](checker::Type const *const exprType) -> bool { return exprType != nullptr && (exprType->IsETSObjectType() || exprType->IsETSTypeParameter()); }; program->Ast()->TransformChildrenRecursively( // clang-format off [this, ctx, &hasIterator](ir::AstNode *ast) -> ir::AstNode* { // clang-format on if (ast->IsForOfStatement()) { if (auto const *const exprType = ast->AsForOfStatement()->Right()->TsType(); hasIterator(exprType) || (exprType != nullptr && exprType->IsETSUnionType() && exprType->AsETSUnionType()->AllOfConstituentTypes(hasIterator))) { return ProcessObjectIterator(ctx, ast->AsForOfStatement()); } } return ast; }, Name()); return true; } } // namespace ark::es2panda::compiler