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// 17// desc: For-of-loop syntax is translated to the while-loop syntax by calling of special method 18// providing predefined 'iterator' interface: 19// for (let x of c) { // c is an object of 'iterable' class 20// <body> 21// } 22// ... 23// let_ci_=_c.$_iterator() 24// let_it_=_ci.next() 25// while_(!it.done)_{ 26// x_=_it.value! 27// <body> 28// it_=_ci.next() 29// } 30// 31 32#include "objectIterator.h" 33 34#include "parser/ETSparser.h" 35#include "compiler/lowering/util.h" 36#include "compiler/lowering/scopesInit/scopesInitPhase.h" 37#include "checker/ETSchecker.h" 38 39namespace ark::es2panda::compiler { 40 41static constexpr std::size_t const WHILE_LOOP_POSITION = 2U; 42static constexpr std::size_t const WHILE_LOOP_SIZE = 2U; 43 44std::string_view ObjectIteratorLowering::Name() const 45{ 46 static std::string const NAME = "ObjectIteratorLowering"; 47 return NAME; 48} 49 50void ObjectIteratorLowering::TransferForOfLoopBody(ir::Statement *const forBody, ir::BlockStatement *const whileBody, 51 bool const needCleaning) const noexcept 52{ 53 ASSERT(forBody != nullptr && whileBody != nullptr); 54 auto &whileStatements = whileBody->Statements(); 55 56 // Currently while loop body consists of 2 statements: 'x = it.value!' and 'it = ci.next()' 57 // We need to insert the body of original for-of-loop between them, change their parent and 58 // probably clean types for expressions and variables for identifier for subsequent re-check. 59 if (forBody->IsBlockStatement()) { 60 auto &forStatements = forBody->AsBlockStatement()->Statements(); 61 std::size_t const forSize = forStatements.size(); 62 63 whileStatements.resize(WHILE_LOOP_SIZE + forSize); 64 whileStatements[WHILE_LOOP_SIZE + forSize - 1U] = whileStatements[WHILE_LOOP_SIZE - 1U]; 65 66 for (std::size_t i = 0U; i < forSize; ++i) { 67 auto &statement = forStatements[i]; 68 statement->SetParent(whileBody); 69 if (needCleaning) { 70 // Note: we don't need to clean top-level statement itself because it doesn't have type. 71 ClearTypesVariablesAndScopes(statement); 72 } 73 whileStatements[WHILE_LOOP_SIZE + i - 1U] = statement; 74 } 75 } else { 76 whileStatements.resize(WHILE_LOOP_SIZE + 1U); 77 whileStatements[WHILE_LOOP_SIZE] = whileStatements[WHILE_LOOP_SIZE - 1U]; 78 79 forBody->SetParent(whileBody); 80 if (needCleaning) { 81 ClearTypesVariablesAndScopes(forBody); 82 } 83 whileStatements[WHILE_LOOP_SIZE - 1U] = forBody; 84 } 85} 86 87ir::Statement *ObjectIteratorLowering::ProcessObjectIterator(parser::ETSParser *parser, checker::ETSChecker *checker, 88 varbinder::ETSBinder *varbinder, 89 ir::ForOfStatement *forOfStatement) const 90{ 91 // Note! We assume that parser, varbinder and checker phases have been already passed correctly, thus the 92 // class has required accessible iterator method and all the types and scopes are properly resolved. 93 94 auto *const allocator = checker->Allocator(); 95 auto statementScope = varbinder::LexicalScope<varbinder::Scope>::Enter(varbinder, NearestScope(forOfStatement)); 96 97 ir::Identifier *const iterIdent = Gensym(allocator); 98 ir::Identifier *const nextIdent = Gensym(allocator); 99 std::string loopVariableName; 100 bool declared = true; 101 102 // Replace the for-of loop with the while loop using the provided iterator interface 103 std::string whileStatement = "let @@I1 = (@@E2)." + std::string {compiler::Signatures::ITERATOR_METHOD} + "(); "; 104 whileStatement += "let @@I3 = @@I4.next(); "; 105 whileStatement += "while (!@@I5.done) { "; 106 107 if (auto *const left = forOfStatement->Left(); left->IsVariableDeclaration()) { 108 auto *const declaration = left->AsVariableDeclaration(); 109 whileStatement += 110 declaration->Kind() != ir::VariableDeclaration::VariableDeclarationKind::CONST ? "let " : "const "; 111 loopVariableName = declaration->Declarators().at(0U)->Id()->AsIdentifier()->Name().Mutf8(); 112 } else if (left->IsIdentifier()) { 113 declared = false; 114 loopVariableName = left->AsIdentifier()->Name().Mutf8(); 115 } else { 116 UNREACHABLE(); 117 } 118 119 whileStatement += loopVariableName + " = @@I6.value!; "; 120 // later on here we will insert the current for-of-loop body. 121 whileStatement += "@@I7 = @@I8.next(); }"; 122 123 // Parse ArkTS code string and create corresponding AST nodes 124 auto *const loweringResult = parser->CreateFormattedStatement( 125 whileStatement, iterIdent, forOfStatement->Right(), nextIdent, iterIdent->Clone(allocator, nullptr), 126 nextIdent->Clone(allocator, nullptr), nextIdent->Clone(allocator, nullptr), 127 nextIdent->Clone(allocator, nullptr), iterIdent->Clone(allocator, nullptr)); 128 loweringResult->SetParent(forOfStatement->Parent()); 129 130 TransferForOfLoopBody(forOfStatement->Body(), 131 loweringResult->AsBlockStatement() 132 ->Statements()[WHILE_LOOP_POSITION] 133 ->AsWhileStatement() 134 ->Body() 135 ->AsBlockStatement(), 136 declared); 137 138 InitScopesPhaseETS::RunExternalNode(loweringResult, varbinder); 139 loweringResult->Check(checker); 140 141 return loweringResult; 142} 143 144bool ObjectIteratorLowering::Perform(public_lib::Context *ctx, parser::Program *program) 145{ 146 const auto &options = ctx->config->options->CompilerOptions(); 147 if (options.compilationMode == CompilationMode::GEN_STD_LIB) { 148 for (auto &[_, extPrograms] : program->ExternalSources()) { 149 (void)_; 150 for (auto *extProg : extPrograms) { 151 Perform(ctx, extProg); 152 } 153 } 154 } 155 156 auto *const parser = ctx->parser->AsETSParser(); 157 ASSERT(parser != nullptr); 158 auto *const checker = ctx->checker->AsETSChecker(); 159 ASSERT(checker != nullptr); 160 auto *const varbinder = ctx->checker->VarBinder()->AsETSBinder(); 161 ASSERT(varbinder != nullptr); 162 163 auto hasIterator = [](checker::Type const *const exprType) -> bool { 164 return exprType != nullptr && 165 ((exprType->IsETSObjectType() && !exprType->IsETSStringType()) || exprType->IsETSTypeParameter()); 166 }; 167 168 program->Ast()->TransformChildrenRecursively( 169 // clang-format off 170 [this, parser, checker, varbinder, &hasIterator](ir::AstNode *ast) -> ir::AstNode* { 171 // clang-format on 172 if (ast->IsForOfStatement()) { 173 if (auto const *const exprType = ast->AsForOfStatement()->Right()->TsType(); 174 hasIterator(exprType) || (exprType != nullptr && exprType->IsETSUnionType() && 175 exprType->AsETSUnionType()->AllOfConstituentTypes(hasIterator))) { 176 return ProcessObjectIterator(parser, checker, varbinder, ast->AsForOfStatement()); 177 } 178 } 179 return ast; 180 }, 181 Name()); 182 183 return true; 184} 185} // namespace ark::es2panda::compiler 186