From 29667dbc42b6ee5297397d541f6e48351384d662 Mon Sep 17 00:00:00 2001 From: Robert Sipka Date: Wed, 25 Jun 2025 13:56:45 +0200 Subject: [PATCH] Implement a setter lowering step Issue: ICHTT6 Internal Issue: 26801 Saving the value in a temporary variable, which is then used both for the setter call and any subsequent uses Change-Id: I5c67ed5d4642ca5be9623fad7347dba0c65988c9 Signed-off-by: Robert Sipka --- ets2panda/BUILD.gn | 1 + ets2panda/CMakeLists.txt | 1 + .../compiler/lowering/ets/setterLowering.cpp | 122 ++++++++++++++++++ .../compiler/lowering/ets/setterLowering.h | 35 +++++ ets2panda/compiler/lowering/phase.cpp | 2 + ets2panda/test/runtime/ets/26801_1.ets | 34 +++++ ets2panda/test/runtime/ets/26801_2.ets | 29 +++++ 7 files changed, 224 insertions(+) create mode 100644 ets2panda/compiler/lowering/ets/setterLowering.cpp create mode 100644 ets2panda/compiler/lowering/ets/setterLowering.h create mode 100644 ets2panda/test/runtime/ets/26801_1.ets create mode 100644 ets2panda/test/runtime/ets/26801_2.ets diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 6d25361c19d..3dcc4001401 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -249,6 +249,7 @@ libes2panda_sources = [ "compiler/lowering/ets/restArgsLowering.cpp", "compiler/lowering/ets/restTupleLowering.cpp", "compiler/lowering/ets/setJumpTarget.cpp", + "compiler/lowering/ets/setterLowering.cpp", "compiler/lowering/ets/spreadLowering.cpp", "compiler/lowering/ets/stringComparison.cpp", "compiler/lowering/ets/stringConstantsLowering.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index c53de4f112e..029a4a36ca7 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -322,6 +322,7 @@ set(ES2PANDA_LIB_SRC compiler/lowering/ets/enumPostCheckLowering.cpp compiler/lowering/ets/enumPropertiesInAnnotationsLowering.cpp compiler/lowering/ets/setJumpTarget.cpp + compiler/lowering/ets/setterLowering.cpp compiler/lowering/ets/annotationCopyLowering.cpp compiler/lowering/ets/annotationCopyPostLowering.cpp compiler/lowering/ets/primitiveConversionPhase.cpp diff --git a/ets2panda/compiler/lowering/ets/setterLowering.cpp b/ets2panda/compiler/lowering/ets/setterLowering.cpp new file mode 100644 index 00000000000..5a9f03e33eb --- /dev/null +++ b/ets2panda/compiler/lowering/ets/setterLowering.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include "setterLowering.h" + +#include "checker/ETSchecker.h" +#include "compiler/lowering/util.h" +#include "compiler/lowering/scopesInit/scopesInitPhase.h" +namespace ark::es2panda::compiler { + +static ir::Expression *GetClone(ArenaAllocator *allocator, ir::Expression *node) +{ + return node == nullptr ? nullptr : node->Clone(allocator, nullptr)->AsExpression(); +} + +static std::string GetFormatPlaceholder(const ir::Expression *expr, const size_t counter) +{ + if (expr->IsIdentifier()) { + return "@@I" + std::to_string(counter); + } + + return "@@E" + std::to_string(counter); +} + +static bool IsSetterCall(checker::ETSChecker *checker, ir::Expression *expr) +{ + if (!expr->IsMemberExpression()) { + return false; + } + + auto memberExpr = expr->AsMemberExpression(); + auto property = memberExpr->Property(); + ES2PANDA_ASSERT(property != nullptr); + + auto variable = property->Variable(); + if (!checker->IsVariableGetterSetter(variable) || variable->Declaration() == nullptr || + variable->Declaration()->Node() == nullptr) { + return false; + } + + auto propDeclNode = variable->Declaration()->Node(); + + return propDeclNode->IsMethodDefinition() && propDeclNode->AsMethodDefinition()->Function()->IsSetter(); +} + +static ir::AstNode *TransformSetterCall(public_lib::Context *ctx, ir::AssignmentExpression *const assignmentExpression) +{ + auto *const allocator = ctx->Allocator(); + auto *parser = ctx->parser->AsETSParser(); + + ES2PANDA_ASSERT(assignmentExpression->Left()->IsMemberExpression()); + auto memberExpr = assignmentExpression->Left()->AsMemberExpression(); + ir::Identifier *id1 = Gensym(allocator); + std::string newAssignmentStatement = "const @@I1 = " + GetFormatPlaceholder(assignmentExpression->Right(), 2U) + + ";(" + GetFormatPlaceholder(memberExpr->Object(), 3U) + "." + + GetFormatPlaceholder(memberExpr->Property(), 4U) + + ") = " + GetFormatPlaceholder(assignmentExpression->Right(), 5U) + "; @@I6;"; + + auto formattedExpr = parser->CreateFormattedExpression( + newAssignmentStatement, id1, assignmentExpression->Right(), memberExpr->Object(), memberExpr->Property(), + GetClone(allocator, assignmentExpression->Right()), GetClone(allocator, id1)); + + return formattedExpr; +} + +using AstNodePtr = ir::AstNode *; + +bool SetterLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) +{ + program->Ast()->TransformChildrenRecursively( + [ctx](ir::AstNode *const node) -> AstNodePtr { + // When a setter call appears in an expression where the value depends on another variable + // such as in an assignment or a comparison, a getter call would required to retrieve that value. + // With saving the value into a temporary variable, it can be avoided, so thats why this setter call + // is transformed during this lowering step + // Example: + // y = c.field = 10; -> Both the getter and the setter are required here + // It will be transformed to -> y = ({const gensym%%_67 = 10; c.field = 10; gensym%%_67}); + if (!node->IsAssignmentExpression()) { + return node; + } + + auto assignmentExpr = node->AsAssignmentExpression(); + auto left = assignmentExpr->Left(); + auto *checker = ctx->GetChecker()->AsETSChecker(); + if (!IsSetterCall(checker, left)) { + return node; + } + + auto loweringResult = TransformSetterCall(ctx, assignmentExpr); + loweringResult->SetParent(assignmentExpr->Parent()); + + auto *const scope = NearestScope(assignmentExpr); + auto expressionCtx = varbinder::LexicalScope::Enter(checker->VarBinder(), scope); + InitScopesPhaseETS::RunExternalNode(loweringResult, checker->VarBinder()); + checker->VarBinder()->AsETSBinder()->ResolveReferencesForScopeWithContext(loweringResult, scope); + + checker::SavedCheckerContext scc {checker, checker::CheckerStatus::IGNORE_VISIBILITY, + ContainingClass(assignmentExpr)}; + checker::ScopeContext sc {checker, scope}; + loweringResult->Check(checker); + + return loweringResult; + }, + Name()); + + return true; +} + +} // namespace ark::es2panda::compiler diff --git a/ets2panda/compiler/lowering/ets/setterLowering.h b/ets2panda/compiler/lowering/ets/setterLowering.h new file mode 100644 index 00000000000..baf591dc7fe --- /dev/null +++ b/ets2panda/compiler/lowering/ets/setterLowering.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef ES2PANDA_COMPILER_LOWERING_SETTER_LOWERING_H +#define ES2PANDA_COMPILER_LOWERING_SETTER_LOWERING_H + +#include "compiler/lowering/phase.h" + +namespace ark::es2panda::compiler { + +class SetterLowering : public PhaseForDeclarations { +public: + std::string_view Name() const override + { + return "SetterLowering"; + } + + bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; +}; + +} // namespace ark::es2panda::compiler + +#endif diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index 18eefa5f2dd..43ce100f070 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -63,6 +63,7 @@ #include "compiler/lowering/ets/lateInitialization.h" #include "compiler/lowering/ets/restArgsLowering.h" #include "compiler/lowering/ets/setJumpTarget.h" +#include "compiler/lowering/ets/setterLowering.h" #include "compiler/lowering/ets/spreadLowering.h" #include "compiler/lowering/ets/stringComparison.h" #include "compiler/lowering/ets/stringConstantsLowering.h" @@ -141,6 +142,7 @@ std::vector GetETSPhaseList() new ArrayLiteralLowering, new BigIntLowering, new OpAssignmentLowering, + new SetterLowering, new LateInitializationConvert, new ExtensionAccessorPhase, new BoxingForLocals, diff --git a/ets2panda/test/runtime/ets/26801_1.ets b/ets2panda/test/runtime/ets/26801_1.ets new file mode 100644 index 00000000000..947574d97ad --- /dev/null +++ b/ets2panda/test/runtime/ets/26801_1.ets @@ -0,0 +1,34 @@ +/* + * 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. + */ + + +let x=0, staticX=0, y=0, staticY=0; +class C { + set x(v:int) { x = v; } + static set staticX(v:int) { staticX = v; } + set y(v:int) { y = v; } + static set staticY(v:int) { staticY = v; } +} + +function main(): void { + arktest.assertEQ(new C().x = 1, 1, "`new C().x = 1` is `1`"); + arktest.assertEQ(x, 1, "The value of `x` is `1`"); + arktest.assertEQ(C.staticX = 2, 2, "`C.staticX = 2` is `2`"); + arktest.assertEQ(staticX, 2, "The value of `staticX` is `2`"); + arktest.assertEQ(new C().y = 3, 3, "`new C().y = 3` is `3`"); + arktest.assertEQ(y, 3, "The value of `y` is `3`"); + arktest.assertEQ(C.staticY = 4, 4, "`C.staticY = 4` is `4`"); + arktest.assertEQ(staticY, 4, "The value of `staticY` is `4`"); +} diff --git a/ets2panda/test/runtime/ets/26801_2.ets b/ets2panda/test/runtime/ets/26801_2.ets new file mode 100644 index 00000000000..6afa50ed2d4 --- /dev/null +++ b/ets2panda/test/runtime/ets/26801_2.ets @@ -0,0 +1,29 @@ +/* + * 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 C { + set field(arg: int) {} + get field(): int { return 1;} +} + +let y: int = 0; + +function main(): void { + let c = new C(); + y = c.field = 10 + + arktest.assertEQ(y, 10); + arktest.assertEQ(c.field, 1); +} -- Gitee