From 111904884ab4fc59e7cd5be9c25e12df4616a88d Mon Sep 17 00:00:00 2001 From: Csaba Hurton Date: Thu, 24 Jul 2025 12:16:41 +0200 Subject: [PATCH 1/4] Fix err: Cant find imported element transitively Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICBIRP Testing: - all necessary test suits pass Fixes #23856 internal issue. Change-Id: I175aefdabc3afbeb32a0b1188fe3864fa8e24472 Signed-off-by: Csaba Hurton --- ets2panda/varbinder/ETSBinder.cpp | 23 +++++++++++++---------- ets2panda/varbinder/ETSBinder.h | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ets2panda/varbinder/ETSBinder.cpp b/ets2panda/varbinder/ETSBinder.cpp index 9305148e05..06208a27ee 100644 --- a/ets2panda/varbinder/ETSBinder.cpp +++ b/ets2panda/varbinder/ETSBinder.cpp @@ -225,16 +225,17 @@ util::StringView ETSBinder::FindNameInAliasMap(const util::StringView &pathAsKey return ""; } -const ir::AstNode *ETSBinder::FindNodeInAliasMap(const util::StringView &pathAsKey, const util::StringView &aliasName) +std::pair ETSBinder::FindNameAndNodeInAliasMap(const util::StringView &pathAsKey, + const util::StringView &aliasName) { if (auto relatedMap = selectiveExportAliasMultimap_.find(pathAsKey); relatedMap != selectiveExportAliasMultimap_.end()) { if (auto item = relatedMap->second.find(aliasName); item != relatedMap->second.end()) { - return item->second.second; + return item->second; } } - return nullptr; + return std::pair("", nullptr); } void ETSBinder::LookupIdentReference(ir::Identifier *ident) @@ -993,7 +994,7 @@ bool ETSBinder::AddImportSpecifiersToTopBindings(Span re } } - util::StringView nameToSearchFor = FindNameInAliasMap(import->ResolvedSource(), imported); + auto [nameToSearchFor, exportNode] = FindNameAndNodeInAliasMap(import->ResolvedSource(), imported); if (nameToSearchFor.Empty()) { nameToSearchFor = imported; } @@ -1002,17 +1003,17 @@ bool ETSBinder::AddImportSpecifiersToTopBindings(Span re importSpecifier->Imported()->SetVariable(var); importSpecifier->Local()->SetVariable(var); + bool varFoundThroughReExport = false; if (var == nullptr) { var = AddImportSpecifierFromReExport(importSpecifier, import, imported, importPath); + varFoundThroughReExport = var != nullptr; } if (var == nullptr) { return false; } - auto *node = FindNodeInAliasMap(import->ResolvedSource(), imported); - - ValidateImportVariable(node != nullptr ? node : var->Declaration()->Node(), var->Declaration()->Node(), imported, - importPath); + ValidateImportVariable(exportNode != nullptr ? exportNode : var->Declaration()->Node(), var->Declaration()->Node(), + imported, importPath); const auto localName = importSpecifier->Local()->Name(); auto varInGlobalClassScope = Program()->GlobalClassScope()->FindLocal(localName, ResolveBindingOptions::ALL); @@ -1030,12 +1031,11 @@ bool ETSBinder::AddImportSpecifiersToTopBindings(Span re // The first part of the condition will be true, if something was given an alias when exported, but we try // to import it using its original name and if original name is not exported. - if (nameToSearchFor == imported && var->Declaration()->Node()->HasExportAlias() && + if ((exportNode == nullptr && !varFoundThroughReExport) && var->Declaration()->Node()->HasExportAlias() && !var->Declaration()->Node()->IsExported()) { ThrowError(importSpecifier->Start(), diagnostic::IMPORT_NOT_FOUND, {imported}); return false; } - InsertOrAssignForeignBinding(localName, var); return true; } @@ -1437,6 +1437,9 @@ Variable *ETSBinder::ValidateImportSpecifier(const ir::ImportSpecifier *const sp !item->AsImportSpecifier()->Local()->Name().Is(item->AsImportSpecifier()->Imported()->Name().Mutf8())) { imported = item->AsImportSpecifier()->Imported()->Name(); } + if (auto foundAlias = FindNameInAliasMap(importProgram->AbsoluteName(), imported); !foundAlias.Is("")) { + imported = foundAlias; + } } auto *const var = FindImportSpecifiersVariable(imported, globalBindings, Span {records}); diff --git a/ets2panda/varbinder/ETSBinder.h b/ets2panda/varbinder/ETSBinder.h index 32b930c322..025f4c30d9 100644 --- a/ets2panda/varbinder/ETSBinder.h +++ b/ets2panda/varbinder/ETSBinder.h @@ -267,7 +267,8 @@ public: } util::StringView FindNameInAliasMap(const util::StringView &pathAsKey, const util::StringView &aliasName); - const ir::AstNode *FindNodeInAliasMap(const util::StringView &pathAsKey, const util::StringView &aliasName); + std::pair FindNameAndNodeInAliasMap(const util::StringView &pathAsKey, + const util::StringView &aliasName); void CleanUp() override { -- Gitee From 5a9d73d264801adcf517ffcf0159d94a530b233e Mon Sep 17 00:00:00 2001 From: Aleksander Sotov Date: Wed, 16 Jul 2025 13:33:31 +0300 Subject: [PATCH 2/4] String class drop number in indexing Issue: https://gitee.com/openharmony/arkcompiler_runtime_core/issues/ICM9OG?from=project-issue Testing: all tests passed Signed-off-by: Aleksander Sotov --- .../test/ast/parser/ets/FixedArray/StringFasta.ets | 10 +++++----- ets2panda/test/ast/parser/ets/StringFasta.ets | 10 +++++----- .../code_point_at.ets | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ets2panda/test/ast/parser/ets/FixedArray/StringFasta.ets b/ets2panda/test/ast/parser/ets/FixedArray/StringFasta.ets index 3cc0fa0bcd..54308f81cd 100644 --- a/ets2panda/test/ast/parser/ets/FixedArray/StringFasta.ets +++ b/ets2panda/test/ast/parser/ets/FixedArray/StringFasta.ets @@ -157,16 +157,16 @@ function main(): void { /* @@? 53:27 Error TypeError: Type 'null' cannot be assigned to type 'Char' */ /* @@? 54:26 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 54:57 Error TypeError: 'For-of' statement source expression is not of iterable type. */ -/* @@? 71:33 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 72:24 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 77:33 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 78:24 Error TypeError: Type 'Double' has no call signatures. */ +/* @@? 71:33 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 72:24 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 77:33 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 78:24 Error TypeError: Type 'Int' has no call signatures. */ /* @@? 84:41 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 86:9 Error TypeError: Static property 'makeCumulative' must be accessed through it's class 'StringFasta' */ /* @@? 94:34 Error TypeError: Static property 'Random' must be accessed through it's class 'StringFasta' */ /* @@? 95:34 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 95:65 Error TypeError: 'For-of' statement source expression is not of iterable type. */ -/* @@? 103:20 Error TypeError: Type 'Double' has no call signatures. */ +/* @@? 103:20 Error TypeError: Type 'Int' has no call signatures. */ /* @@? 112:16 Error TypeError: Static property 'fastaRepeat' must be accessed through it's class 'StringFasta' */ /* @@? 112:32 Error TypeError: Property 'count' must be accessed through 'this' */ /* @@? 113:16 Error TypeError: Static property 'fastaRandom' must be accessed through it's class 'StringFasta' */ diff --git a/ets2panda/test/ast/parser/ets/StringFasta.ets b/ets2panda/test/ast/parser/ets/StringFasta.ets index 758d82839f..47e9de0529 100644 --- a/ets2panda/test/ast/parser/ets/StringFasta.ets +++ b/ets2panda/test/ast/parser/ets/StringFasta.ets @@ -157,16 +157,16 @@ function main(): void { /* @@? 53:27 Error TypeError: Type 'null' cannot be assigned to type 'Char' */ /* @@? 54:26 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 54:57 Error TypeError: 'For-of' statement source expression is not of iterable type. */ -/* @@? 71:33 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 72:24 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 77:33 Error TypeError: Type 'Double' has no call signatures. */ -/* @@? 78:24 Error TypeError: Type 'Double' has no call signatures. */ +/* @@? 71:33 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 72:24 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 77:33 Error TypeError: Type 'Int' has no call signatures. */ +/* @@? 78:24 Error TypeError: Type 'Int' has no call signatures. */ /* @@? 84:41 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 86:9 Error TypeError: Static property 'makeCumulative' must be accessed through it's class 'StringFasta' */ /* @@? 94:34 Error TypeError: Static property 'Random' must be accessed through it's class 'StringFasta' */ /* @@? 95:34 Error TypeError: Cannot find type 'HashMap'. */ /* @@? 95:65 Error TypeError: 'For-of' statement source expression is not of iterable type. */ -/* @@? 103:20 Error TypeError: Type 'Double' has no call signatures. */ +/* @@? 103:20 Error TypeError: Type 'Int' has no call signatures. */ /* @@? 112:16 Error TypeError: Static property 'fastaRepeat' must be accessed through it's class 'StringFasta' */ /* @@? 112:32 Error TypeError: Property 'count' must be accessed through 'this' */ /* @@? 113:16 Error TypeError: Static property 'fastaRandom' must be accessed through it's class 'StringFasta' */ diff --git a/ets2panda/test/runtime/ets/const_variable_in_switch_statement/code_point_at.ets b/ets2panda/test/runtime/ets/const_variable_in_switch_statement/code_point_at.ets index b08891c755..d77bb5f8a9 100644 --- a/ets2panda/test/runtime/ets/const_variable_in_switch_statement/code_point_at.ets +++ b/ets2panda/test/runtime/ets/const_variable_in_switch_statement/code_point_at.ets @@ -13,9 +13,9 @@ * limitations under the License. */ -const a = ('X'.codePointAt(0) as Double).toInt() +const a = ('X'.codePointAt(0)!).toInt() arktest.assertEQ(a, 88); -const b = ('x'.codePointAt(0) as Double).toInt() +const b = ('x'.codePointAt(0)!).toInt() arktest.assertEQ(b, 120); let c = 88 let d = 0 -- Gitee From 34327a626cb9e2fcab2d0f573103208e3f0d2ce1 Mon Sep 17 00:00:00 2001 From: Martin Sajti Date: Wed, 30 Jul 2025 13:30:01 +0200 Subject: [PATCH 3/4] Fix indexable object with assignment chaining Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICPWGT Internal issue: #27655 Indexable object '$_set' calls emit value loading with '$_get' if chained. To match the behaviour with accessor methods (getter, setter), the implementation is changed, such that only 'set' calls are emitted. Test: build, runtime tests Change-Id: I0878e37fdb7d3a7bdfdd6dd672853efcf2f88b8b Signed-off-by: Martin Sajti --- .../lowering/ets/objectIndexAccess.cpp | 91 ++++++------------- .../compiler/lowering/ets/objectIndexAccess.h | 8 +- .../compiler/lowering/ets/setterLowering.cpp | 50 +++++----- ets2panda/compiler/lowering/phase.cpp | 2 +- ets2panda/test/runtime/ets/index_chain_1.ets | 63 +++++++++++++ 5 files changed, 124 insertions(+), 90 deletions(-) create mode 100644 ets2panda/test/runtime/ets/index_chain_1.ets diff --git a/ets2panda/compiler/lowering/ets/objectIndexAccess.cpp b/ets2panda/compiler/lowering/ets/objectIndexAccess.cpp index c57d29aa9c..16d194cd48 100644 --- a/ets2panda/compiler/lowering/ets/objectIndexAccess.cpp +++ b/ets2panda/compiler/lowering/ets/objectIndexAccess.cpp @@ -24,57 +24,29 @@ #include "objectIndexAccess.h" #include "checker/ETSchecker.h" -#include "checker/types/typeFlag.h" #include "compiler/lowering/util.h" #include "parser/ETSparser.h" -#include "util/options.h" namespace ark::es2panda::compiler { -ir::Expression *ObjectIndexLowering::ProcessIndexSetAccess(parser::ETSParser *parser, checker::ETSChecker *checker, - ir::AssignmentExpression *assignmentExpression) const +static ir::AstNode *ProcessIndexSetAccess(public_lib::Context *ctx, ir::AssignmentExpression *assignmentExpression) { // Note! We assume that parser and checker phase nave been already passed correctly, thus the class has // required accessible index method[s] and all the types are properly resolved. - auto indexSymbol = Gensym(checker->Allocator()); + auto *const parser = ctx->parser->AsETSParser(); + auto *const checker = ctx->GetChecker()->AsETSChecker(); auto *const memberExpression = assignmentExpression->Left()->AsMemberExpression(); - ir::Expression *loweringResult = nullptr; - ir::AstNode *setter = nullptr; - // Generate call to $_get to handle the chained assignment expression - if (assignmentExpression->Parent()->IsExpression() || assignmentExpression->Parent()->IsVariableDeclarator()) { - ArenaVector blockStatements(checker->Allocator()->Adapter()); - auto objectSymbol = Gensym(checker->Allocator()); - blockStatements.push_back( - parser->CreateFormattedStatement("let @@I1 = @@E2", objectSymbol, memberExpression->Object())); - blockStatements.push_back( - parser->CreateFormattedStatement("let @@I1 = @@E2", indexSymbol, memberExpression->Property())); - static std::string const CALL_EXPRESSION = - std::string {"@@E1."} + std::string {compiler::Signatures::SET_INDEX_METHOD} + "(@@E2, @@E3)"; - // Parse ArkTS code string and create and process corresponding AST node(s) - auto *const setStmt = parser->CreateFormattedStatement( - CALL_EXPRESSION, objectSymbol->Clone(checker->Allocator(), nullptr), - indexSymbol->Clone(checker->Allocator(), nullptr), assignmentExpression->Right()); - setter = setStmt; - blockStatements.push_back(setStmt); - static std::string const GET_EXPRESSION = - std::string {"@@E1."} + std::string {compiler::Signatures::GET_INDEX_METHOD} + "(@@E2)"; - blockStatements.push_back(parser->CreateFormattedStatement(GET_EXPRESSION, - objectSymbol->Clone(checker->Allocator(), nullptr), - indexSymbol->Clone(checker->Allocator(), nullptr))); - loweringResult = - util::NodeAllocator::ForceSetParent(checker->Allocator(), std::move(blockStatements)); - } else { - static std::string const CALL_EXPRESSION = - std::string {"@@E1."} + std::string {compiler::Signatures::SET_INDEX_METHOD} + "(@@E2, @@E3)"; - // Parse ArkTS code string and create and process corresponding AST node(s) - loweringResult = parser->CreateFormattedExpression(CALL_EXPRESSION, memberExpression->Object(), - memberExpression->Property(), assignmentExpression->Right()); - setter = loweringResult; - } - ES2PANDA_ASSERT(loweringResult != nullptr && setter != nullptr); + + static std::string const CALL_EXPRESSION = + std::string {"@@E1."} + std::string {compiler::Signatures::SET_INDEX_METHOD} + "(@@E2, @@E3)"; + + ir::Expression *loweringResult = parser->CreateFormattedExpression( + CALL_EXPRESSION, memberExpression->Object(), memberExpression->Property(), assignmentExpression->Right()); + + ES2PANDA_ASSERT(loweringResult != nullptr); loweringResult->SetParent(assignmentExpression->Parent()); loweringResult->SetRange(assignmentExpression->Range()); - setter->AddModifier(ir::ModifierFlags::ARRAY_SETTER); + loweringResult->AddModifier(ir::ModifierFlags::ARRAY_SETTER); auto scope = varbinder::LexicalScope::Enter(checker->VarBinder(), NearestScope(assignmentExpression->Parent())); CheckLoweredNode(checker->VarBinder()->AsETSBinder(), checker, loweringResult); @@ -83,13 +55,15 @@ ir::Expression *ObjectIndexLowering::ProcessIndexSetAccess(parser::ETSParser *pa return loweringResult; } -ir::Expression *ObjectIndexLowering::ProcessIndexGetAccess(parser::ETSParser *parser, checker::ETSChecker *checker, - ir::MemberExpression *memberExpression) const +static ir::AstNode *ProcessIndexGetAccess(public_lib::Context *ctx, ir::MemberExpression *memberExpression) { + auto *const parser = ctx->parser->AsETSParser(); + auto *const checker = ctx->GetChecker()->AsETSChecker(); + // Note! We assume that parser and checker phase nave been already passed correctly, thus the class has // required accessible index method[s] and all the types are properly resolved. static std::string const CALL_EXPRESSION = - std::string {"@@E1."} + std::string {compiler::Signatures::GET_INDEX_METHOD} + "(@@E2)"; + std::string {"@@E1."} + std::string {Signatures::GET_INDEX_METHOD} + "(@@E2)"; // Parse ArkTS code string and create and process corresponding AST node(s) auto *const loweringResult = @@ -104,20 +78,17 @@ ir::Expression *ObjectIndexLowering::ProcessIndexGetAccess(parser::ETSParser *pa bool ObjectIndexLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) { - auto *const parser = ctx->parser->AsETSParser(); - ES2PANDA_ASSERT(parser != nullptr); - auto *const checker = ctx->GetChecker()->AsETSChecker(); - ES2PANDA_ASSERT(checker != nullptr); + const auto isGetSetExpression = [](const ir::MemberExpression *const memberExpr) { + return memberExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS && memberExpr->ObjType() != nullptr && + !memberExpr->Object()->TsType()->IsGradualType(); + }; program->Ast()->TransformChildrenRecursively( - // CC-OFFNXT(G.FMT.14-CPP) project code style - [this, parser, checker](ir::AstNode *const ast) -> ir::AstNode * { + [ctx, &isGetSetExpression](ir::AstNode *const ast) { if (ast->IsAssignmentExpression() && ast->AsAssignmentExpression()->Left()->IsMemberExpression()) { - auto memberExpr = ast->AsAssignmentExpression()->Left()->AsMemberExpression(); - if (memberExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS && - memberExpr->AsMemberExpression()->ObjType() != nullptr && - !memberExpr->Object()->TsType()->IsGradualType()) { - return ProcessIndexSetAccess(parser, checker, ast->AsAssignmentExpression()); + const auto *const memberExpr = ast->AsAssignmentExpression()->Left()->AsMemberExpression(); + if (isGetSetExpression(memberExpr)) { + return ProcessIndexSetAccess(ctx, ast->AsAssignmentExpression()); } } return ast; @@ -125,13 +96,11 @@ bool ObjectIndexLowering::PerformForModule(public_lib::Context *ctx, parser::Pro Name()); program->Ast()->TransformChildrenRecursively( - // CC-OFFNXT(G.FMT.14-CPP) project code style - [this, parser, checker](ir::AstNode *const ast) -> ir::AstNode * { + [ctx, &isGetSetExpression](ir::AstNode *const ast) { if (ast->IsMemberExpression()) { - auto memberExpr = ast->AsMemberExpression(); - if (memberExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS && - memberExpr->ObjType() != nullptr && !memberExpr->Object()->TsType()->IsGradualType()) { - return ProcessIndexGetAccess(parser, checker, ast->AsMemberExpression()); + auto *const memberExpr = ast->AsMemberExpression(); + if (isGetSetExpression(memberExpr)) { + return ProcessIndexGetAccess(ctx, memberExpr); } } return ast; @@ -146,7 +115,7 @@ bool ObjectIndexLowering::PostconditionForModule([[maybe_unused]] public_lib::Co { return !program->Ast()->IsAnyChild([](const ir::AstNode *ast) { if (ast->IsMemberExpression()) { - auto memberExpr = ast->AsMemberExpression(); + const auto *const memberExpr = ast->AsMemberExpression(); if (memberExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS && memberExpr->ObjType() != nullptr && !memberExpr->Object()->TsType()->IsGradualType()) { return true; diff --git a/ets2panda/compiler/lowering/ets/objectIndexAccess.h b/ets2panda/compiler/lowering/ets/objectIndexAccess.h index baff9dfbc2..4b06a33b0a 100644 --- a/ets2panda/compiler/lowering/ets/objectIndexAccess.h +++ b/ets2panda/compiler/lowering/ets/objectIndexAccess.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * 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 @@ -33,12 +33,6 @@ public: bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; bool PostconditionForModule(public_lib::Context *ctx, const parser::Program *program) override; - -private: - ir::Expression *ProcessIndexGetAccess(parser::ETSParser *parser, checker::ETSChecker *checker, - ir::MemberExpression *memberExpression) const; - ir::Expression *ProcessIndexSetAccess(parser::ETSParser *parser, checker::ETSChecker *checker, - ir::AssignmentExpression *assignmentExpression) const; }; } // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/lowering/ets/setterLowering.cpp b/ets2panda/compiler/lowering/ets/setterLowering.cpp index 902fbbc667..50f2d01a2c 100644 --- a/ets2panda/compiler/lowering/ets/setterLowering.cpp +++ b/ets2panda/compiler/lowering/ets/setterLowering.cpp @@ -20,22 +20,21 @@ #include "compiler/lowering/scopesInit/scopesInitPhase.h" namespace ark::es2panda::compiler { -// This lowering transforms setter calls, to preserve the parameter value, so it can be passed after that. -// The transformation can be written like this -// Original code +// This lowering transforms setter calls and indexable object set calls, to preserve the parameter value, so it can be +// passed after that. The transformation can be written like this Original code // ---------------- -// = // where is a setter property +// = // where is a setter property or a set call on an indexable object // ---------------- // Transformed // ---------------- -// const gensym = ; -// = gensym; +// const gensym: ; +// = gensym := ; // the whole rhs is is block expression here, to not introduce a new chained assignment // gensym; // ---------------- // The main purpose of the modification is to support cases like this // (x, y are simple variables; c.x is a setter property) // x = c.x = y -static bool IsSetterCall(const ir::Expression *const expr) +static bool IsSetterCallOrSetExpression(const ir::Expression *const expr) { if (!expr->IsMemberExpression()) { return false; @@ -43,15 +42,21 @@ static bool IsSetterCall(const ir::Expression *const expr) const auto *memberExpr = expr->AsMemberExpression(); const auto *property = memberExpr->Property(); + auto *const variable = property->Variable(); - auto *variable = property->Variable(); - if (!checker::ETSChecker::IsVariableGetterSetter(variable)) { - return false; + if (checker::ETSChecker::IsVariableGetterSetter(variable)) { + ES2PANDA_ASSERT(variable != nullptr && variable->TsType() != nullptr); + return variable->TsType()->HasTypeFlag(checker::TypeFlag::SETTER); } - ES2PANDA_ASSERT(variable != nullptr && variable->TsType() != nullptr); + // We are already checking the left side of an assignment expression, so the condition below will only match for + // set expressions, but not get expressions + const auto isSetExpression = [](const ir::MemberExpression *const possibleSetExpr) { + return possibleSetExpr->Kind() == ir::MemberExpressionKind::ELEMENT_ACCESS && + possibleSetExpr->ObjType() != nullptr && !possibleSetExpr->Object()->TsType()->IsGradualType(); + }; - return variable->TsType()->HasTypeFlag(checker::TypeFlag::SETTER); + return isSetExpression(memberExpr); } static ir::Expression *TransformSetterCall(public_lib::Context *ctx, @@ -60,24 +65,27 @@ static ir::Expression *TransformSetterCall(public_lib::Context *ctx, auto *const allocator = ctx->Allocator(); auto *const parser = ctx->parser->AsETSParser(); - ir::Identifier *gensym1 = Gensym(allocator); - auto *gensymType = ctx->AllocNode(assignmentExpression->Right()->TsType(), allocator); + ir::Identifier *gensymValue = Gensym(allocator); + auto *gensymValueType = ctx->AllocNode(assignmentExpression->Right()->TsType(), allocator); + + auto *setGensymAndEvalValue = parser->CreateFormattedExpression( + "@@I1 = @@E2", gensymValue->Clone(allocator, nullptr), assignmentExpression->Right()); std::stringstream ss; - ss << "const @@I1: @@T2 = @@E3;"; - ss << "@@E4 = @@I5;"; - ss << "(@@I6);"; + ss << "let @@I1: @@T2;"; + ss << "@@E3 = @@E4;"; + ss << "(@@I5);"; - return parser->CreateFormattedExpression(ss.str(), gensym1, gensymType, assignmentExpression->Right(), - assignmentExpression->Left(), gensym1->Clone(allocator, nullptr), - gensym1->Clone(allocator, nullptr)); + return parser->CreateFormattedExpression(ss.str(), gensymValue, gensymValueType, assignmentExpression->Left(), + setGensymAndEvalValue, gensymValue->Clone(allocator, nullptr)); } bool SetterLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) { program->Ast()->TransformChildrenRecursively( [ctx](ir::AstNode *const node) { - if (!node->IsAssignmentExpression() || !IsSetterCall(node->AsAssignmentExpression()->Left())) { + if (!node->IsAssignmentExpression() || + !IsSetterCallOrSetExpression(node->AsAssignmentExpression()->Left())) { return node; } diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index 4dd62cd22f..47a89ade9e 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -141,7 +141,7 @@ std::vector GetETSPhaseList() new ArrayLiteralLowering, new BigIntLowering, new OpAssignmentLowering, - new SetterLowering, + new SetterLowering, // must be put before ObjectIndexLowering new LateInitializationConvert, new ExtensionAccessorPhase, new BoxingForLocals, diff --git a/ets2panda/test/runtime/ets/index_chain_1.ets b/ets2panda/test/runtime/ets/index_chain_1.ets new file mode 100644 index 0000000000..851577b728 --- /dev/null +++ b/ets2panda/test/runtime/ets/index_chain_1.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 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. + */ + +function testArr(a: int[]): void { + let x = a[1] = a[2] = 1; + arktest.assertEQ(a[0], 6); + arktest.assertEQ(a[1], 1); + arktest.assertEQ(a[2], 1); + arktest.assertEQ(a[3], 9); +} + +let resSetter: string = "" +let resIdx: string = "" + +class TestSetIdx { + $_get(idx: number): number { + arktest.assertTrue(false); + return 0; + } + $_set(idx: number, val: number) { + resSetter += idx; + arktest.assertEQ(val, 201); + } +} + +function getIdx(a0: number): number { + resIdx += a0; + return a0; +} + +function testNoGetCall() { + let testCls = new TestSetIdx() + testCls[getIdx(9)] = testCls[getIdx(8)] = testCls[getIdx(7)] = 201; + resSetter += "_"; + resIdx += "_"; + let a: number = 201.0; + let b = a = testCls[getIdx(13)] = testCls[getIdx(14)] = a = a = testCls[getIdx(15)] = a; + arktest.assertEQ(b, 201.0); +} + +function main(): int { + resSetter = "" + resIdx = "" + + testArr([6,7,8,9]); + testNoGetCall(); + arktest.assertEQ(resSetter, "789_151413"); + arktest.assertEQ(resIdx, "987_131415"); + + return 0; +} -- Gitee From 5fa3771a794fd0ccee315fd94160577575e7d314 Mon Sep 17 00:00:00 2001 From: Zelentsov Dmitry Date: Thu, 31 Jul 2025 10:38:38 +0300 Subject: [PATCH 4/4] Fix function with receiver processing Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICQ3MS Tests: use Test-U-Runner and CI Signed-off-by: Zelentsov Dmitry --- ets2panda/checker/ETSAnalyzer.cpp | 54 ++++++++++++------- ets2panda/checker/ETSAnalyzerHelpers.cpp | 19 +++---- ets2panda/checker/ETSchecker.h | 5 +- ets2panda/checker/ets/function.cpp | 24 ++++++--- ets2panda/compiler/core/function.cpp | 4 +- .../ast/compiler/ets/interface_literal.ets | 31 +++++++++++ .../ast/compiler/ets/method-assignment_01.ets | 41 ++++++++++++++ ets2panda/util/helpers.cpp | 2 +- ets2panda/util/helpers.h | 2 +- ets2panda/varbinder/varbinder.cpp | 2 +- 10 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 ets2panda/test/ast/compiler/ets/interface_literal.ets create mode 100644 ets2panda/test/ast/compiler/ets/method-assignment_01.ets diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 9af93405a2..3c45b7a149 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -25,10 +25,8 @@ #include "types/signature.h" #include "compiler/lowering/ets/setJumpTarget.h" #include "checker/types/ets/etsAsyncFuncReturnType.h" -#include "types/ts/nullType.h" #include "types/type.h" #include "checker/types/typeError.h" -#include "util/es2pandaMacros.h" #include @@ -1171,30 +1169,50 @@ checker::Type *ETSAnalyzer::GetSmartType(ir::AssignmentExpression *expr, checker return smartType; } -static bool IsMethodDefinition(const ir::Expression *const expression) +static ir::MethodDefinition const *ResolveMethodDefinition(const ir::Expression *const expression, ETSChecker *checker) { - return expression->IsMemberExpression() && expression->AsMemberExpression()->Property() != nullptr && - expression->AsMemberExpression()->Property()->Variable() != nullptr && - expression->AsMemberExpression()->Property()->Variable()->Declaration() != nullptr && - expression->AsMemberExpression()->Property()->Variable()->Declaration()->Node()->IsMethodDefinition(); + if (!expression->IsMemberExpression()) { + return nullptr; + } + + auto const *memberExpression = expression->AsMemberExpression(); + if (memberExpression->Kind() != ir::MemberExpressionKind::PROPERTY_ACCESS || + memberExpression->Property() == nullptr || !memberExpression->Property()->IsIdentifier()) { + return nullptr; + } + + auto const *variable = memberExpression->Property()->Variable(); + if (variable == nullptr) { + if (auto const *objectType = memberExpression->Object()->TsType(); + objectType != nullptr && objectType->IsETSObjectType()) { + // Process possible case of the same name method with receiver defined + auto resolved = checker->ResolveMemberReference(memberExpression, objectType->AsETSObjectType()); + if (resolved.size() == 2U && resolved[1]->Kind() == checker::ResolvedKind::PROPERTY) { + variable = resolved[1U]->Variable()->AsLocalVariable(); + } + } + } + + if (variable != nullptr) { + if (variable->Declaration() != nullptr && variable->Declaration()->Node()->IsMethodDefinition()) { + return variable->Declaration()->Node()->AsMethodDefinition(); + } + } + + return nullptr; } static bool IsInvalidMethodAssignment(const ir::AssignmentExpression *const expr, ETSChecker *checker) { auto left = expr->Left(); - if (IsMethodDefinition(left)) { - { - auto methodDefinition = - left->AsMemberExpression()->Property()->Variable()->Declaration()->Node()->AsMethodDefinition(); - if (!methodDefinition->IsSetter() && - std::none_of(methodDefinition->Overloads().cbegin(), methodDefinition->Overloads().cend(), - [](const auto *overload) { return overload->IsSetter(); })) { - checker->LogError(diagnostic::METHOD_ASSIGNMENT, expr->Left()->Start()); - return true; - } + if (auto const *methodDefinition = ResolveMethodDefinition(left, checker); methodDefinition != nullptr) { + if (!methodDefinition->IsSetter() && + std::none_of(methodDefinition->Overloads().cbegin(), methodDefinition->Overloads().cend(), + [](const auto *overload) { return overload->IsSetter(); })) { + checker->LogError(diagnostic::METHOD_ASSIGNMENT, left->Start()); + return true; } } - return false; } diff --git a/ets2panda/checker/ETSAnalyzerHelpers.cpp b/ets2panda/checker/ETSAnalyzerHelpers.cpp index 2075563dcd..d3127e33aa 100644 --- a/ets2panda/checker/ETSAnalyzerHelpers.cpp +++ b/ets2panda/checker/ETSAnalyzerHelpers.cpp @@ -404,18 +404,19 @@ checker::Signature *GetMostSpecificSigFromExtensionFuncAndClassMethod(checker::E // For allCallSignatures in ClassMethodType, temporarily insert the dummyReceiver into their signatureInfo, // otherwise we can't get the most suitable classMethod signature if all the extensionFunction signature mismatched. ArenaVector signatures(checker->ProgramAllocator()->Adapter()); - signatures.insert(signatures.end(), type->ClassMethodType()->CallSignatures().begin(), - type->ClassMethodType()->CallSignatures().end()); - signatures.insert(signatures.end(), type->ExtensionMethodType()->CallSignatures().begin(), - type->ExtensionMethodType()->CallSignatures().end()); + auto const &classMethodSignatures = type->ClassMethodType()->CallSignatures(); + auto const &extensionMethodSignatures = type->ExtensionMethodType()->CallSignaturesOfMethodOrArrow(); + + signatures.insert(signatures.end(), classMethodSignatures.cbegin(), classMethodSignatures.cend()); + signatures.insert(signatures.end(), extensionMethodSignatures.cbegin(), extensionMethodSignatures.cend()); auto *memberExpr = expr->Callee()->AsMemberExpression(); auto *dummyReceiver = memberExpr->Object(); - auto *dummyReceiverVar = type->ExtensionMethodType()->CallSignatures()[0]->Params()[0]; + auto *dummyReceiverVar = extensionMethodSignatures[0]->Params()[0]; expr->Arguments().insert(expr->Arguments().begin(), dummyReceiver); const bool typeParamsNeeded = dummyReceiverVar->TsType()->IsETSObjectType(); - for (auto *methodCallSig : type->ClassMethodType()->CallSignatures()) { + for (auto *methodCallSig : classMethodSignatures) { methodCallSig->GetSignatureInfo()->minArgCount++; auto ¶msVar = methodCallSig->Params(); paramsVar.insert(paramsVar.begin(), dummyReceiverVar); @@ -431,7 +432,7 @@ checker::Signature *GetMostSpecificSigFromExtensionFuncAndClassMethod(checker::E auto *signature = checker->ResolveCallExpressionAndTrailingLambda(signatures, expr, expr->Start(), checker::TypeRelationFlag::NO_THROW); - for (auto *methodCallSig : type->ClassMethodType()->CallSignatures()) { + for (auto *methodCallSig : classMethodSignatures) { methodCallSig->GetSignatureInfo()->minArgCount--; auto ¶msVar = methodCallSig->Params(); paramsVar.erase(paramsVar.begin()); @@ -479,8 +480,8 @@ checker::Signature *ResolveCallForETSExtensionFuncHelperType(checker::ETSExtensi signature = GetMostSpecificSigFromExtensionFuncAndClassMethod(type, checker, expr); if (signature == nullptr) { - checker->ThrowSignatureMismatch(type->ExtensionMethodType()->CallSignatures(), expr->Arguments(), expr->Start(), - "call"); + checker->ThrowSignatureMismatch(type->ExtensionMethodType()->CallSignaturesOfMethodOrArrow(), expr->Arguments(), + expr->Start(), "call"); } return signature; diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 490d90f351..ff2029c0ea 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -480,8 +480,9 @@ public: bool SetPreferredTypeForArrayArgument(ir::ArrayExpression *arrayExpr, Signature *substitutedSig); bool ValidateSignatureRestParams(Signature *substitutedSig, const ArenaVector &arguments, TypeRelationFlag flags, bool reportError, bool unique); - void ThrowSignatureMismatch(ArenaVector &signatures, const ArenaVector &arguments, - const lexer::SourcePosition &pos, std::string_view signatureKind); + void ThrowSignatureMismatch(ArenaVector const &signatures, + const ArenaVector &arguments, const lexer::SourcePosition &pos, + std::string_view signatureKind); Signature *FirstMatchSignatures(ir::CallExpression *expr, checker::Type *calleeType); Signature *MatchOrderSignatures(ArenaVector &signatures, const ir::TSTypeParameterInstantiation *typeArguments, diff --git a/ets2panda/checker/ets/function.cpp b/ets2panda/checker/ets/function.cpp index 6f65e600f6..15eb82a032 100644 --- a/ets2panda/checker/ets/function.cpp +++ b/ets2panda/checker/ets/function.cpp @@ -845,7 +845,7 @@ Signature *ETSChecker::GetMostSpecificSignature(ArenaVector &compat return mostSpecificSignature; } -void ETSChecker::ThrowSignatureMismatch(ArenaVector &signatures, +void ETSChecker::ThrowSignatureMismatch(ArenaVector const &signatures, const ArenaVector &arguments, const lexer::SourcePosition &pos, std::string_view signatureKind) { @@ -855,7 +855,7 @@ void ETSChecker::ThrowSignatureMismatch(ArenaVector &signatures, if (someSignature->HasFunction()) { if (someSignature->Function()->IsConstructor()) { - msg.append(util::Helpers::GetClassDefiniton(someSignature->Function())->InternalName().Mutf8()); + msg.append(util::Helpers::GetClassDefinition(someSignature->Function())->InternalName().Mutf8()); } else { msg.append(someSignature->Function()->Id()->Name().Mutf8()); } @@ -917,6 +917,16 @@ Signature *ETSChecker::ValidateSignatures(ArenaVector &signatures, return nullptr; } +// Excluded from 'FindMostSpecificSignature' to reduce its size due to code-style check +static std::size_t GetParameterNumber(Signature const *const sig) +{ + if (sig->HasFunction()) { + return sig->Function()->Params().size(); + } + auto num = sig->Params().size(); + return !sig->HasRestParameter() ? num : ++num; +} + Signature *ETSChecker::FindMostSpecificSignature(const ArenaVector &signatures, const ArenaMultiMap &bestSignaturesForParameter, size_t paramCount) @@ -941,25 +951,25 @@ Signature *ETSChecker::FindMostSpecificSignature(const ArenaVector continue; } - const auto candidateLength = candidate->Function()->Params().size(); + const auto candidateLength = GetParameterNumber(candidate); if (candidateLength > currentMinLength && !candidate->HasRestParameter()) { continue; } if (result == nullptr) { result = candidate; // First valid candidate - currentMinLength = result->Function()->Params().size(); + currentMinLength = GetParameterNumber(result); continue; } - const auto currentLength = result->Function()->Params().size(); + const auto currentLength = GetParameterNumber(result); if (candidate->HasRestParameter() && result->HasRestParameter()) { if (result->Owner() == candidate->Owner()) { result = nullptr; } } else if (candidateLength < currentLength) { result = candidate; // Shorter parameter count wins - currentMinLength = result->Function()->Params().size(); + currentMinLength = GetParameterNumber(result); } else if (candidateLength >= currentLength) { continue; // NOTE (smartin): all other cases below are unreachable code @@ -1050,7 +1060,7 @@ void ETSChecker::SearchAmongMostSpecificTypes(Type *&mostSpecificType, Signature { auto [pos, idx, sig] = info; Type *sigType = GetParameterTypeOrRestAtIdx(this, sig, idx); - if (prevSig->Function()->Params()[idx]->IsETSParameterExpression()) { + if (prevSig->HasFunction() && prevSig->Function()->Params()[idx]->IsETSParameterExpression()) { Relation()->SetNode(prevSig->Function()->Params()[idx]->AsETSParameterExpression()); } const bool isClassType = diff --git a/ets2panda/compiler/core/function.cpp b/ets2panda/compiler/core/function.cpp index e5c05ca78b..e548223ac8 100644 --- a/ets2panda/compiler/core/function.cpp +++ b/ets2panda/compiler/core/function.cpp @@ -197,7 +197,7 @@ void Function::IterateOverElements(const ArenaVector &elements, P void Function::CompileInstanceFields(PandaGen *pg, const ir::ScriptFunction *decl) { - const auto klass = util::Helpers::GetClassDefiniton(decl); + const auto klass = util::Helpers::GetClassDefinition(decl); const auto &elements = klass->Body(); RegScope rs(pg); @@ -227,7 +227,7 @@ static void CompileFunction(PandaGen *pg) { const auto *decl = pg->RootNode()->AsScriptFunction(); - if (decl->IsConstructor() && (util::Helpers::GetClassDefiniton(decl)->Super() == nullptr)) { + if (decl->IsConstructor() && (util::Helpers::GetClassDefinition(decl)->Super() == nullptr)) { Function::CompileInstanceFields(pg, decl); } diff --git a/ets2panda/test/ast/compiler/ets/interface_literal.ets b/ets2panda/test/ast/compiler/ets/interface_literal.ets new file mode 100644 index 0000000000..81abf38433 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/interface_literal.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 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. + */ + +interface I { + f(x: int): int; +} + +let f = (this: I, x: int): int => x; + +function main() { + let obj: I = { + /* @@ label */f: f + } + + let r: int = obj.f(5); + arktest.assertEQ(r, 5); +} + +/* @@@ label Error TypeError: Class or interface methods cannot be initialized within an object literal. */ diff --git a/ets2panda/test/ast/compiler/ets/method-assignment_01.ets b/ets2panda/test/ast/compiler/ets/method-assignment_01.ets new file mode 100644 index 0000000000..e572ac6981 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/method-assignment_01.ets @@ -0,0 +1,41 @@ +/* + * Copyright (c) 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. + */ + +class A { + f(): void { + console.println("OK"); + } +} + +let f = (this: A): void => { + throw new Error("Boom"); +} + +function main() { + let a: A = new A(); + + /* @@ label */a.f = f; + + try { + a.f(); + f(a); + } catch (e) { + if (e instanceof Error) { + arktest.assertEQ(e.message, "Boom"); + } + } +} + +/* @@@ label Error TypeError: Class methods cannot be overwritten. */ diff --git a/ets2panda/util/helpers.cpp b/ets2panda/util/helpers.cpp index 998094a4bd..51c7b324e4 100644 --- a/ets2panda/util/helpers.cpp +++ b/ets2panda/util/helpers.cpp @@ -325,7 +325,7 @@ const ir::ScriptFunction *Helpers::GetContainingFunction(const ir::AstNode *node return nullptr; } -const ir::ClassDefinition *Helpers::GetClassDefiniton(const ir::ScriptFunction *node) +const ir::ClassDefinition *Helpers::GetClassDefinition(const ir::ScriptFunction *node) { ES2PANDA_ASSERT(node->IsConstructor()); ES2PANDA_ASSERT(node->Parent()->IsFunctionExpression()); diff --git a/ets2panda/util/helpers.h b/ets2panda/util/helpers.h index a1a7ed6cd7..396ff2c401 100644 --- a/ets2panda/util/helpers.h +++ b/ets2panda/util/helpers.h @@ -145,7 +145,7 @@ public: static const ir::MethodDefinition *GetContainingClassMethodDefinition(const ir::AstNode *node); static const ir::ClassStaticBlock *GetContainingClassStaticBlock(const ir::AstNode *node); static const ir::ScriptFunction *GetContainingFunction(const ir::AstNode *node); - static const ir::ClassDefinition *GetClassDefiniton(const ir::ScriptFunction *node); + static const ir::ClassDefinition *GetClassDefinition(const ir::ScriptFunction *node); static bool IsSpecialPropertyKey(const ir::Expression *expr); static bool IsConstantPropertyKey(const ir::Expression *expr, bool isComputed); static compiler::Literal ToConstantLiteral(const ir::Expression *expr); diff --git a/ets2panda/varbinder/varbinder.cpp b/ets2panda/varbinder/varbinder.cpp index a905eae27e..73ef1c50e4 100644 --- a/ets2panda/varbinder/varbinder.cpp +++ b/ets2panda/varbinder/varbinder.cpp @@ -658,7 +658,7 @@ void VarBinder::AddMandatoryParams() const ir::ScriptFunction *ctor = util::Helpers::GetContainingConstructor(scriptFunc); bool lexicalFunctionObject {}; - if (ctor != nullptr && util::Helpers::GetClassDefiniton(ctor)->Super() != nullptr && + if (ctor != nullptr && util::Helpers::GetClassDefinition(ctor)->Super() != nullptr && funcScope->HasFlag(ScopeFlags::USE_SUPER)) { ES2PANDA_ASSERT(ctor->Scope()->HasFlag(ScopeFlags::INNER_ARROW)); ctor->Scope()->AddFlag(ScopeFlags::SET_LEXICAL_FUNCTION); -- Gitee