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// This is a sample lowering, of little value by itself. 18// 19// desc: A compound assignment expression of the form E1 op= E2 is equivalent to E1 = 20// ((E1) op (E2)) as T, where T is the type of E1, except that E1 is evaluated only 21// once. 22// 23 24#include "opAssignment.h" 25 26#include "parser/ETSparser.h" 27#include "varbinder/ETSBinder.h" 28#include "checker/ETSchecker.h" 29#include "compiler/lowering/util.h" 30#include "compiler/lowering/scopesInit/scopesInitPhase.h" 31#include "ir/opaqueTypeNode.h" 32#include "ir/expressions/assignmentExpression.h" 33#include "ir/expressions/identifier.h" 34#include "ir/expressions/memberExpression.h" 35#include "ir/expressions/blockExpression.h" 36#include "ir/statements/blockStatement.h" 37#include "ir/statements/expressionStatement.h" 38 39namespace ark::es2panda::compiler { 40 41struct Conversion { 42 lexer::TokenType from; 43 lexer::TokenType to; 44}; 45 46// NOLINTNEXTLINE(readability-magic-numbers) 47static constexpr std::array<Conversion, 18> OP_TRANSLATION {{ 48 {lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_UNSIGNED_RIGHT_SHIFT}, 49 {lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_RIGHT_SHIFT}, 50 {lexer::TokenType::PUNCTUATOR_LEFT_SHIFT_EQUAL, lexer::TokenType::PUNCTUATOR_LEFT_SHIFT}, 51 {lexer::TokenType::PUNCTUATOR_PLUS_EQUAL, lexer::TokenType::PUNCTUATOR_PLUS}, 52 {lexer::TokenType::PUNCTUATOR_MINUS_EQUAL, lexer::TokenType::PUNCTUATOR_MINUS}, 53 {lexer::TokenType::PUNCTUATOR_MULTIPLY_EQUAL, lexer::TokenType::PUNCTUATOR_MULTIPLY}, 54 {lexer::TokenType::PUNCTUATOR_DIVIDE_EQUAL, lexer::TokenType::PUNCTUATOR_DIVIDE}, 55 {lexer::TokenType::PUNCTUATOR_MOD_EQUAL, lexer::TokenType::PUNCTUATOR_MOD}, 56 {lexer::TokenType::PUNCTUATOR_BITWISE_AND_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_AND}, 57 {lexer::TokenType::PUNCTUATOR_BITWISE_OR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_OR}, 58 {lexer::TokenType::PUNCTUATOR_BITWISE_XOR_EQUAL, lexer::TokenType::PUNCTUATOR_BITWISE_XOR}, 59 {lexer::TokenType::PUNCTUATOR_LOGICAL_AND_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_AND}, 60 {lexer::TokenType::PUNCTUATOR_LOGICAL_OR_EQUAL, lexer::TokenType::PUNCTUATOR_LOGICAL_OR}, 61 {lexer::TokenType::PUNCTUATOR_LOGICAL_NULLISH_EQUAL, lexer::TokenType::PUNCTUATOR_NULLISH_COALESCING}, 62 {lexer::TokenType::PUNCTUATOR_EXPONENTIATION_EQUAL, lexer::TokenType::PUNCTUATOR_EXPONENTIATION}, 63 {lexer::TokenType::PUNCTUATOR_PLUS_PLUS, lexer::TokenType::PUNCTUATOR_PLUS}, 64 {lexer::TokenType::PUNCTUATOR_MINUS_MINUS, lexer::TokenType::PUNCTUATOR_MINUS}, 65}}; 66 67static lexer::TokenType CombinedOpToOp(const lexer::TokenType combinedOp) 68{ 69 for (const auto &conv : OP_TRANSLATION) { 70 if (conv.from == combinedOp) { 71 return conv.to; 72 } 73 } 74 UNREACHABLE(); 75} 76 77void AdjustBoxingUnboxingFlags(ir::Expression *loweringResult, const ir::Expression *oldExpr) 78{ 79 ir::Expression *exprToProcess = nullptr; 80 if (loweringResult->IsAssignmentExpression()) { 81 exprToProcess = loweringResult->AsAssignmentExpression(); 82 } else if (loweringResult->IsBlockExpression() && !loweringResult->AsBlockExpression()->Statements().empty()) { 83 auto *statement = loweringResult->AsBlockExpression()->Statements().back(); 84 if (statement->IsExpressionStatement()) { 85 exprToProcess = statement->AsExpressionStatement()->GetExpression(); 86 } 87 } else { 88 UNREACHABLE(); 89 } 90 91 // NOTE: gogabr. make sure that the checker never puts both a boxing and an unboxing flag on the same node. 92 // Then this function will become unnecessary. 93 const ir::BoxingUnboxingFlags oldBoxingFlag {oldExpr->GetBoxingUnboxingFlags() & 94 ir::BoxingUnboxingFlags::BOXING_FLAG}; 95 const ir::BoxingUnboxingFlags oldUnboxingFlag {oldExpr->GetBoxingUnboxingFlags() & 96 ir::BoxingUnboxingFlags::UNBOXING_FLAG}; 97 98 if (exprToProcess->TsType()->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE)) { 99 loweringResult->SetBoxingUnboxingFlags(oldBoxingFlag); 100 } else if (exprToProcess->TsType()->IsETSObjectType()) { 101 loweringResult->SetBoxingUnboxingFlags(oldUnboxingFlag); 102 } 103} 104 105static ir::OpaqueTypeNode *CreateProxyTypeNode(checker::ETSChecker *checker, ir::Expression *expr) 106{ 107 auto *lcType = expr->TsType(); 108 if (auto *lcTypeAsPrimitive = checker->ETSBuiltinTypeAsPrimitiveType(lcType); lcTypeAsPrimitive != nullptr) { 109 lcType = lcTypeAsPrimitive; 110 } 111 return checker->AllocNode<ir::OpaqueTypeNode>(lcType); 112} 113 114static std::string GenFormatForExpression(ir::Expression *expr, size_t ix1, size_t ix2) 115{ 116 std::string res = "@@I" + std::to_string(ix1); 117 if (expr->IsMemberExpression()) { 118 auto const kind = expr->AsMemberExpression()->Kind(); 119 if (kind == ir::MemberExpressionKind::PROPERTY_ACCESS) { 120 res += ".@@I" + std::to_string(ix2); 121 } else if (kind == ir::MemberExpressionKind::ELEMENT_ACCESS) { 122 res += "[@@I" + std::to_string(ix2) + "]"; 123 } 124 } 125 return res; 126} 127 128static std::string GenerateStringForLoweredAssignment(lexer::TokenType opEqual, ir::Expression *expr) 129{ 130 std::string leftHand = GenFormatForExpression(expr, 5, 6); 131 std::string rightHand = GenFormatForExpression(expr, 7, 8); 132 133 return leftHand + " = (" + rightHand + ' ' + std::string {lexer::TokenToString(CombinedOpToOp(opEqual))} + 134 " (@@E9)) as @@T10"; 135} 136 137static ir::Identifier *GetClone(ArenaAllocator *allocator, ir::Identifier *node) 138{ 139 return node == nullptr ? nullptr : node->Clone(allocator, nullptr); 140} 141 142static ir::Expression *ConstructOpAssignmentResult(public_lib::Context *ctx, ir::AssignmentExpression *assignment) 143{ 144 auto *allocator = ctx->allocator; 145 auto *parser = ctx->parser->AsETSParser(); 146 auto *checker = ctx->checker->AsETSChecker(); 147 148 const auto opEqual = assignment->OperatorType(); 149 ASSERT(opEqual != lexer::TokenType::PUNCTUATOR_SUBSTITUTION); 150 151 auto *const left = assignment->Left(); 152 auto *const right = assignment->Right(); 153 154 std::string newAssignmentStatements {}; 155 156 ir::Identifier *ident1; 157 ir::Identifier *ident2 = nullptr; 158 ir::Expression *object = nullptr; 159 ir::Expression *property = nullptr; 160 161 // Create temporary variable(s) if left hand of assignment is not defined by simple identifier[s] 162 if (left->IsIdentifier()) { 163 ident1 = left->AsIdentifier(); 164 } else if (left->IsMemberExpression()) { 165 auto *const memberExpression = left->AsMemberExpression(); 166 167 if (object = memberExpression->Object(); object->IsIdentifier()) { 168 ident1 = object->AsIdentifier(); 169 } else { 170 ident1 = Gensym(allocator); 171 newAssignmentStatements = "const @@I1 = (@@E2); "; 172 } 173 174 if (property = memberExpression->Property(); property->IsIdentifier()) { 175 ident2 = property->AsIdentifier(); 176 } else { 177 ident2 = Gensym(allocator); 178 newAssignmentStatements += "const @@I3 = (@@E4); "; 179 } 180 } else { 181 UNREACHABLE(); 182 } 183 184 auto *exprType = CreateProxyTypeNode(checker, left); 185 186 // Generate ArkTS code string for new lowered assignment expression: 187 newAssignmentStatements += GenerateStringForLoweredAssignment(opEqual, left); 188 189 // Parse ArkTS code string and create corresponding AST node(s) 190 return parser->CreateFormattedExpression(newAssignmentStatements, ident1, object, ident2, property, 191 GetClone(allocator, ident1), GetClone(allocator, ident2), 192 GetClone(allocator, ident1), GetClone(allocator, ident2), right, exprType); 193} 194 195ir::AstNode *HandleOpAssignment(public_lib::Context *ctx, ir::AssignmentExpression *assignment) 196{ 197 auto *checker = ctx->checker->AsETSChecker(); 198 199 if (assignment->TsType() == nullptr) { // hasn't been through checker 200 return assignment; 201 } 202 203 auto *loweringResult = ConstructOpAssignmentResult(ctx, assignment); 204 205 loweringResult->SetParent(assignment->Parent()); 206 // NOTE(dslynko, #19200): required for correct debug-info 207 auto rng = assignment->Range(); 208 loweringResult->SetRange(rng); 209 loweringResult->TransformChildrenRecursively( 210 [rng](auto *node) { 211 node->SetRange(rng); 212 return node; 213 }, 214 ""); 215 216 auto *const scope = NearestScope(assignment); 217 218 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope); 219 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->parserProgram->VarBinder()); 220 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope); 221 222 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(assignment)}; 223 checker::ScopeContext sc {checker, scope}; 224 225 loweringResult->Check(checker); 226 227 AdjustBoxingUnboxingFlags(loweringResult, assignment); 228 229 return loweringResult; 230} 231 232static ir::Expression *ConstructUpdateResult(public_lib::Context *ctx, ir::UpdateExpression *upd) 233{ 234 auto *allocator = ctx->allocator; 235 auto *parser = ctx->parser->AsETSParser(); 236 auto *argument = upd->Argument(); 237 auto *checker = ctx->checker->AsETSChecker(); 238 239 std::string newAssignmentStatements {}; 240 241 ir::Identifier *id1; 242 ir::Identifier *id2 = nullptr; 243 ir::Identifier *id3 = nullptr; 244 ir::Expression *object = nullptr; 245 ir::Expression *property = nullptr; 246 checker::Type *objType = checker->GlobalVoidType(); // placeholder 247 checker::Type *propType = checker->GlobalVoidType(); 248 249 // Parse ArkTS code string and create the corresponding AST node(s) 250 // We have to use extra caution with types and `as` conversions because of smart types, which we cannot reproduce in 251 // re-checking. 252 253 if (argument->IsIdentifier()) { 254 id1 = GetClone(allocator, argument->AsIdentifier()); 255 } else if (argument->IsMemberExpression()) { 256 auto *memberExpression = argument->AsMemberExpression(); 257 258 if (object = memberExpression->Object(); object != nullptr && object->IsIdentifier()) { 259 id1 = GetClone(allocator, object->AsIdentifier()); 260 } else if (object != nullptr) { 261 id1 = Gensym(allocator); 262 newAssignmentStatements = "const @@I1 = (@@E2) as @@T3; "; 263 objType = object->TsType(); 264 } 265 266 if (property = memberExpression->Property(); property != nullptr && property->IsIdentifier()) { 267 id2 = GetClone(allocator, property->AsIdentifier()); 268 } else if (property != nullptr) { 269 id2 = Gensym(allocator); 270 newAssignmentStatements += "const @@I4 = (@@E5) as @@T6; "; 271 propType = property->TsType(); 272 } 273 } 274 275 std::string opSign = lexer::TokenToString(CombinedOpToOp(upd->OperatorType())); 276 277 std::string suffix = (argument->TsType() == checker->GlobalETSBigIntType()) ? "n" : ""; 278 279 // NOLINTBEGIN(readability-magic-numbers) 280 if (upd->IsPrefix()) { 281 newAssignmentStatements += GenFormatForExpression(argument, 7U, 8U) + " = (" + 282 GenFormatForExpression(argument, 9U, 10U) + opSign + " 1" + suffix + ") as @@T11;"; 283 return parser->CreateFormattedExpression( 284 newAssignmentStatements, id1, object, objType, id2, property, propType, GetClone(allocator, id1), 285 GetClone(allocator, id2), GetClone(allocator, id1), GetClone(allocator, id2), argument->TsType()); 286 } 287 288 // upd is postfix 289 id3 = Gensym(allocator); 290 newAssignmentStatements += "const @@I7 = " + GenFormatForExpression(argument, 8, 9) + " as @@T10;" + 291 GenFormatForExpression(argument, 11U, 12U) + " = (@@I13 " + opSign + " 1" + suffix + 292 ") as @@T14; @@I15;"; 293 return parser->CreateFormattedExpression(newAssignmentStatements, id1, object, objType, id2, property, propType, 294 id3, GetClone(allocator, id1), GetClone(allocator, id2), 295 argument->TsType(), GetClone(allocator, id1), GetClone(allocator, id2), 296 GetClone(allocator, id3), argument->TsType(), GetClone(allocator, id3)); 297 // NOLINTEND(readability-magic-numbers) 298} 299 300static ir::AstNode *HandleUpdate(public_lib::Context *ctx, ir::UpdateExpression *upd) 301{ 302 auto *checker = ctx->checker->AsETSChecker(); 303 304 auto *const scope = NearestScope(upd); 305 306 ir::Expression *loweringResult = ConstructUpdateResult(ctx, upd); 307 308 auto expressionCtx = varbinder::LexicalScope<varbinder::Scope>::Enter(checker->VarBinder(), scope); 309 checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, ContainingClass(upd)}; 310 checker::ScopeContext sc {checker, scope}; 311 312 loweringResult->SetParent(upd->Parent()); 313 // NOTE(dslynko, #19200): required for correct debug-info 314 auto rng = upd->Range(); 315 loweringResult->SetRange(rng); 316 loweringResult->TransformChildrenRecursively( 317 [rng](auto *node) { 318 node->SetRange(rng); 319 return node; 320 }, 321 ""); 322 InitScopesPhaseETS::RunExternalNode(loweringResult, ctx->checker->VarBinder()); 323 324 checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, 325 NearestScope(loweringResult)); 326 loweringResult->Check(checker); 327 328 AdjustBoxingUnboxingFlags(loweringResult, upd); 329 330 return loweringResult; 331} 332 333bool OpAssignmentLowering::Perform(public_lib::Context *ctx, parser::Program *program) 334{ 335 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) { 336 for (auto &[_, ext_programs] : program->ExternalSources()) { 337 (void)_; 338 for (auto *extProg : ext_programs) { 339 Perform(ctx, extProg); 340 } 341 } 342 } 343 344 program->Ast()->TransformChildrenRecursively( 345 [ctx](ir::AstNode *ast) { 346 if (ast->IsAssignmentExpression() && 347 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) { 348 return HandleOpAssignment(ctx, ast->AsAssignmentExpression()); 349 } 350 if (ast->IsUpdateExpression()) { 351 return HandleUpdate(ctx, ast->AsUpdateExpression()); 352 } 353 354 return ast; 355 }, 356 Name()); 357 358 return true; 359} 360 361bool OpAssignmentLowering::Postcondition(public_lib::Context *ctx, const parser::Program *program) 362{ 363 auto checkExternalPrograms = [this, ctx](const ArenaVector<parser::Program *> &programs) { 364 for (auto *p : programs) { 365 if (!Postcondition(ctx, p)) { 366 return false; 367 } 368 } 369 return true; 370 }; 371 372 if (ctx->config->options->CompilerOptions().compilationMode == CompilationMode::GEN_STD_LIB) { 373 for (auto &[_, extPrograms] : program->ExternalSources()) { 374 (void)_; 375 if (!checkExternalPrograms(extPrograms)) { 376 return false; 377 }; 378 } 379 } 380 381 return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) { 382 return (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->TsType() != nullptr && 383 ast->AsAssignmentExpression()->OperatorType() != lexer::TokenType::PUNCTUATOR_SUBSTITUTION) || 384 ast->IsUpdateExpression(); 385 }); 386} 387 388} // namespace ark::es2panda::compiler 389