diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 596f03163ccfa9da9069f87a7683f7359b2a5526..a9eb903c18b1bbb2e53ff360a68462e6616fc62f 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -217,6 +217,7 @@ libes2panda_sources = [ "compiler/lowering/ets/exportAnonymousConst.cpp", "compiler/lowering/ets/expressionLambdaLowering.cpp", "compiler/lowering/ets/extensionAccessorLowering.cpp", + "compiler/lowering/ets/fieldOverridingLowering.cpp", "compiler/lowering/ets/genericBridgesLowering.cpp", "compiler/lowering/ets/insertOptionalParametersAnnotation.cpp", "compiler/lowering/ets/interfaceObjectLiteralLowering.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index d40f60c4f49d5841cc4005e2f7a4ad4be8e0bbe6..ed64dd577413066f025936a4154a43f50c21dbc9 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -316,6 +316,7 @@ set(ES2PANDA_LIB_SRC compiler/lowering/ets/stringConstructorLowering.cpp compiler/lowering/ets/typeFromLowering.cpp compiler/lowering/ets/enumLowering.cpp + compiler/lowering/ets/fieldOverridingLowering.cpp compiler/lowering/ets/enumPostCheckLowering.cpp compiler/lowering/ets/enumPropertiesInAnnotationsLowering.cpp compiler/lowering/ets/setJumpTarget.cpp diff --git a/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c41f3922bd11abbba80e9b1194aaf1fe2a81775 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.cpp @@ -0,0 +1,355 @@ +/* + * 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 "fieldOverridingLowering.h" +#include +#include +#include "generated/diagnostic.h" +#include "ir/expressions/memberExpression.h" +#include "utils/arena_containers.h" +#include "checker/ETSchecker.h" +#include "compiler/lowering/util.h" +#include "util/helpers.h" +#include "utils/logger.h" + +/* +BEFORE LOWERING: +class Base { + field: number = 1; +} + +AFTER LOWERING: +class Base { + field: number; + constructor() { + this.field = 1; + } +} + +BEFORE LOWERING: +class Derived extends Base { + field: number = 2; +} + +AFTER LOWERING: +class Derived extends Base { + // field: number = 2; // Field removed from derived class + constructor() { + super(); + this.field = 2; + } +} +*/ + +namespace ark::es2panda::compiler { + +bool operator<(const FieldOveriding::OverriddenFieldInfo &a, const FieldOveriding::OverriddenFieldInfo &b) +{ + return std::tuple {a.derivedClassDef, a.derivedField, a.baseClassDef, a.baseField} < + std::tuple {b.derivedClassDef, b.derivedField, b.baseClassDef, b.baseField}; +} + +static ir::AstNode *GetClassForProp(ir::AstNode *node) +{ + if (node == nullptr) { + return nullptr; + } + for (ir::AstNode *p = node->Parent(); p != nullptr; p = p->Parent()) { + if (p->IsClassDefinition()) { + return p; + } + } + return nullptr; +} + +static std::string ToString(ir::AstNode *node) +{ + ir::AstNode *classDef = GetClassForProp(node); + if (classDef == nullptr) { + return ""; + } + auto classId = classDef->FindChild([](ir::AstNode *n) { return n->IsIdentifier(); }); + auto id = + node->IsIdentifier() ? node->AsIdentifier() : node->FindChild([](ir::AstNode *n) { return n->IsIdentifier(); }); + return id != nullptr && classId != nullptr + ? classId->AsIdentifier()->ToString() + "::" + id->AsIdentifier()->ToString() + : ""; +} + +void TraverseParentClassProperties(ir::AstNode *node, std::function cb) +{ + if (!node->IsClassProperty()) { + return; + } + + ir::AstNode *classDef = nullptr; + for (auto parent = node->Parent(); parent != nullptr; parent = parent->Parent()) { + if (parent->IsClassDefinition()) { + classDef = parent; + } + } + + if (classDef == nullptr) { + return; + } + + ir::AstNode *parentClass = + classDef->IsClassDefinition() && classDef->AsClassDefinition()->Super() != nullptr + ? classDef->AsClassDefinition()->Super()->TsType()->AsETSObjectType()->GetDeclNode()->AsClassDefinition() + : nullptr; // NOTE(muhammet): These checks are pobably unnecessary + while (parentClass != nullptr) { + if (parentClass->IsClassDefinition()) { + const ArenaVector body = parentClass->AsClassDefinition()->Body(); + std::for_each(body.begin(), body.end(), [&cb](ir::AstNode *fieldNode) { + if (fieldNode->IsClassProperty()) { + cb(fieldNode->AsClassProperty()); + } + }); + } + parentClass = parentClass->IsClassDefinition() && parentClass->AsClassDefinition()->Super() != nullptr + ? parentClass->AsClassDefinition() + ->Super() + ->TsType() + ->AsETSObjectType() + ->GetDeclNode() + ->AsClassDefinition() + : nullptr; + } +} + +static ir::AstNode *GetBaseProp(public_lib::Context *ctx, ir::AstNode *node) +{ + if (!node->IsClassProperty()) { + return nullptr; + } + + ir::AstNode *res = nullptr; + + // Get parent class fields + ir::ClassProperty *derivedProp = node->AsClassProperty(); + + // Go through all the parents and check if any of them have a field with the same name and type + TraverseParentClassProperties(derivedProp, [&derivedProp, &ctx, &res](ir::ClassProperty *baseProp) { + bool typesEqual = ctx->GetChecker()->IsTypeIdenticalTo(baseProp->TsType(), derivedProp->TsType()); + bool namesEqual = baseProp->Id()->Name() == derivedProp->Id()->Name(); + bool bothNonStatic = baseProp->IsStatic() == derivedProp->IsStatic() && !baseProp->IsStatic(); + bool isOverriding = typesEqual && namesEqual && bothNonStatic; + bool typeMismatch = bothNonStatic && namesEqual && !typesEqual; + bool accessModifiersCompatible = + (baseProp->IsPublic() && derivedProp->IsPublic()) || + (baseProp->IsProtected() && (derivedProp->IsProtected() || derivedProp->IsPublic())); + // NOTE(muhammet): Couldn't handle case with non readonly derived field + bool readonlyCompatible = baseProp->IsReadonly() == derivedProp->IsReadonly() || + (!baseProp->IsReadonly() && derivedProp->IsReadonly()); + + if (typeMismatch && accessModifiersCompatible) { + // NOTE(muhammet): Move the error messages to the yaml file + ctx->diagnosticEngine->LogSemanticError( + diagnostic::DiagnosticKind { + util::DiagnosticType::SEMANTIC, 1, + "Overriding field has a different type, overriding field: '{}' overriden field '{}'"}, + util::DiagnosticMessageParams {ToString(derivedProp), ToString(baseProp)}, derivedProp->Start()); + } else if (isOverriding && accessModifiersCompatible && readonlyCompatible) { + res = baseProp; + } + }); + + return res; +} + +static ir::Statement *CreateAssignmentStatementForField(public_lib::Context *ctx, ir::AstNode *baseField, + ir::AstNode *derivedField) +{ + if (derivedField->AsClassProperty()->Value() == nullptr || baseField->IsStatic()) { + return nullptr; + } + auto val = derivedField->AsClassProperty()->Value()->Clone(ctx->allocator, nullptr)->AsExpression(); + auto *thisExpr = util::NodeAllocator::Alloc(ctx->allocator); + thisExpr->AsThisExpression()->SetVariable(baseField->Variable()); + auto *fieldIdentifier = util::NodeAllocator::Alloc( + ctx->allocator, baseField->AsClassProperty()->Id()->Name(), ctx->allocator); + fieldIdentifier->SetVariable(baseField->Variable()); + auto *leftHandSide = util::NodeAllocator::Alloc( + ctx->allocator, thisExpr, fieldIdentifier, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); + auto *rightHandSide = val; + rightHandSide->SetVariable(val->Variable()); + auto *initializer = util::NodeAllocator::Alloc( + ctx->allocator, leftHandSide, rightHandSide, lexer::TokenType::PUNCTUATOR_SUBSTITUTION); + if (derivedField->IsReadonly()) { + initializer->SetIgnoreConstAssign(); + } + auto initStatement = util::NodeAllocator::Alloc(ctx->allocator, initializer); + return initStatement; +} + +// Map overriding/derived fields to overridden/base fields +static std::map GetFieldMap(public_lib::Context *ctx, + ir::ClassDefinition *derivedClassDef) +{ + std::map fields; + + // Iterate over class properties + for (auto derivedProp : derivedClassDef->Body()) { + auto baseProp = GetBaseProp(ctx, derivedProp); + if (baseProp != nullptr) { + // counterparts will be removed from the class + fields[derivedProp] = baseProp; + } + } + + return fields; +} + +// If there are any explicit calls to super add assignments after that +static ArenaVector::iterator AssigmentInsertionPosition(ir::BlockStatement *blockStatement) +{ + return !blockStatement->StatementsForUpdates().empty() && + blockStatement->StatementsForUpdates().front()->IsSuperExpression() + ? blockStatement->StatementsForUpdates().begin() + 1 + : blockStatement->StatementsForUpdates().begin(); +} + +ir::AstNode *FieldOveriding::HandleDerivedField(public_lib::Context *ctx, OverriddenFieldInfo ovField) +{ + // Edit ctor + auto ctor = ovField.derivedClassDef + ->FindChild([](ir::AstNode *node) { + return node->IsMethodDefinition() && node->AsMethodDefinition()->IsConstructor(); + }) + ->AsMethodDefinition(); + ir::Statement *initStatement = CreateAssignmentStatementForField(ctx, ovField.baseField, ovField.derivedField); + if (initStatement != nullptr) { + auto blockStatement = ctor->Function()->Body()->AsBlockStatement(); + blockStatement->SetParent(ctor->Function()->Body()->Parent()); + auto pos = AssigmentInsertionPosition(blockStatement); + pos = blockStatement->StatementsForUpdates().emplace(pos, initStatement) + 1; + initStatement->SetParent(blockStatement); + initStatements_.emplace_back(initStatement); + } + + // Edit class body and remove fields + auto found = std::find_if(ovField.derivedClassDef->Body().begin(), ovField.derivedClassDef->Body().end(), + [ovField](ir::AstNode *node) { return node == ovField.derivedField; }); + if (found != ovField.derivedClassDef->Body().end()) { + LOG_INFO(ES2PANDA, false) << "Removing field '" + ToString(*found) + "'"; + ovField.derivedClassDef->BodyForUpdate().erase(found); + // NOTE(muhammet): Cant recheck the class due to checker errors but it works when the module is rechecked, + // couldnt find a fix for this + ir::AstNode *module = nullptr; + for (module = ovField.derivedClassDef->Parent(); module != nullptr; module = module->Parent()) { + if (module->IsETSModule()) { + break; + } + } + ES2PANDA_ASSERT(module != nullptr); + Recheck(GetPhaseManager(), ctx->GetChecker()->AsETSChecker()->VarBinder()->AsETSBinder(), + ctx->GetChecker()->AsETSChecker(), module); + } + + return ovField.derivedClassDef; +} + +ir::AstNode *FieldOveriding::HandleBaseField(public_lib::Context *ctx, OverriddenFieldInfo ovField) +{ + if (ovField.baseField->IsReadonly()) { + return ovField.baseClassDef; + } + // Edit ctor + auto ctor = ovField.baseClassDef + ->FindChild([](ir::AstNode *node) { + return node->IsMethodDefinition() && node->AsMethodDefinition()->IsConstructor(); + }) + ->AsMethodDefinition(); + ir::Statement *initStatement = CreateAssignmentStatementForField(ctx, ovField.baseField, ovField.baseField); + if (initStatement != nullptr) { + auto blockStatement = ctor->Function()->Body()->AsBlockStatement(); + blockStatement->SetParent(ctor->Function()->Body()->Parent()); + auto pos = AssigmentInsertionPosition(blockStatement); + pos = blockStatement->StatementsForUpdates().emplace(pos, initStatement) + 1; + initStatement->SetParent(blockStatement); + initStatements_.emplace_back(initStatement); + } + return ovField.baseClassDef; +} + +// NOTE(muhammet): Might be redundant +void FieldOveriding::RebindIdentifiers([[maybe_unused]] public_lib::Context *ctx, parser::Program *program) +{ + // Replace all references to derived field with base field + auto cb = [this](ir::AstNode *node) { + if (node->IsIdentifier() && node->AsIdentifier()->Variable() != nullptr && + node->AsIdentifier()->Variable()->Declaration() != nullptr) { + auto varNode = node->AsIdentifier()->Variable()->Declaration()->Node(); + OverriddenFieldInfo found {nullptr, nullptr, nullptr, nullptr}; + if (std::any_of(fieldsList_.begin(), fieldsList_.end(), + [varNode, &found](const OverriddenFieldInfo &ovField) { + found = ovField; + return varNode == ovField.derivedField; + })) { + LOG_INFO(ES2PANDA, false) << "Replacing " << ToString(found.derivedField) << " with " + << ToString(found.baseField) << " at " << ToString(node); + node->AsIdentifier()->Variable()->Declaration()->BindNode(found.baseField); + } + } + return node; + }; + program->Ast()->TransformChildrenRecursively(cb, this->Name()); +} + +bool FieldOveriding::PerformForModule(public_lib::Context *ctx, parser::Program *program) +{ + program->Ast()->IterateRecursively([&ctx, this](ir::AstNode *node) { + if (!node->IsClassDefinition()) { + return; + } + auto fields = GetFieldMap(ctx, node->AsClassDefinition()); + if (fields.empty()) { + return; + } + + for (auto entry : fields) { + auto derivedField = entry.first; + auto baseField = entry.second; + auto baseClassDef = GetClassForProp(baseField)->AsClassDefinition(); + auto derivedClassDef = GetClassForProp(derivedField)->AsClassDefinition(); + fieldsList_.insert(OverriddenFieldInfo {derivedClassDef, derivedField, baseClassDef, baseField}); + } + }); + + for (auto ovField : fieldsList_) { + LOG_INFO(ES2PANDA, false) << "Field '" + std::string {ovField.derivedClassDef->Ident()->Name()} + + "::" + std::string {ovField.derivedField->AsClassProperty()->Id()->Name()} + + "' overrides '" + std::string {ovField.baseClassDef->Ident()->Name()} + + "::" + std::string {ovField.baseField->AsClassProperty()->Id()->Name()} + "'"; + + [[maybe_unused]] auto baseClassDef = HandleBaseField(ctx, ovField)->AsClassDefinition(); + + [[maybe_unused]] auto derivedClassDef = HandleDerivedField(ctx, ovField)->AsClassDefinition(); + + ovField.baseClassDef->SetTransformedNode(Name(), ovField.baseClassDef); + ovField.derivedClassDef->SetTransformedNode(Name(), ovField.derivedClassDef); + } + + for (auto node : initStatements_) { + CheckLoweredNode(ctx->GetChecker()->AsETSChecker()->VarBinder()->AsETSBinder(), + ctx->GetChecker()->AsETSChecker(), node); + } + + RebindIdentifiers(ctx, program); + + return true; +} + +} // namespace ark::es2panda::compiler \ No newline at end of file diff --git a/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h new file mode 100644 index 0000000000000000000000000000000000000000..332c1c625c302c80060979ec0b3d8a25162eb5d8 --- /dev/null +++ b/ets2panda/compiler/lowering/ets/fieldOverridingLowering.h @@ -0,0 +1,52 @@ +/* + * 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_FIELD_OVERRIDING_H +#define ES2PANDA_COMPILER_LOWERING_FIELD_OVERRIDING_H + +#include +#include +#include "compiler/lowering/phase.h" + +namespace ark::es2panda::compiler { + +class FieldOveriding : public PhaseForBodies { +public: + struct OverriddenFieldInfo { + ir::ClassDefinition *derivedClassDef; + ir::AstNode *derivedField; + ir::ClassDefinition *baseClassDef; + ir::AstNode *baseField; + }; + + std::string_view Name() const override + { + return "FieldOverriding"; + } + bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; + ir::AstNode *OverrideFields(public_lib::Context *ctx, ir::ClassDefinition *derivedClassDef, + std::map fields, bool isBase = false); + + ir::AstNode *HandleDerivedField(public_lib::Context *ctx, OverriddenFieldInfo ovField); + ir::AstNode *HandleBaseField(public_lib::Context *ctx, OverriddenFieldInfo ovField); + +private: + void RebindIdentifiers(public_lib::Context *ctx, parser::Program *program); + std::set fieldsList_; + std::vector initStatements_; +}; + +} // namespace ark::es2panda::compiler + +#endif // ES2PANDA_COMPILER_LOWERING_FIELD_OVERRIDING_H \ No newline at end of file diff --git a/ets2panda/compiler/lowering/phase.cpp b/ets2panda/compiler/lowering/phase.cpp index c321ed36b8b664e093983492e3613f3e40d3ed51..9443450d57f0b9137bec966b31d6f8159452b35a 100644 --- a/ets2panda/compiler/lowering/phase.cpp +++ b/ets2panda/compiler/lowering/phase.cpp @@ -70,6 +70,7 @@ #include "compiler/lowering/plugin_phase.h" #include "compiler/lowering/resolveIdentifiers.h" #include "compiler/lowering/scopesInit/scopesInitPhase.h" +#include "compiler/lowering/ets/fieldOverridingLowering.h" #include "generated/diagnostic.h" #include "lexer/token/sourceLocation.h" #include "public/es2panda_lib.h" @@ -124,6 +125,7 @@ std::vector GetETSPhaseList() // pluginsAfterCheck has to go right after checkerPhase, nothing should be between them new PluginPhase {g_pluginsAfterCheck, ES2PANDA_STATE_CHECKED, &util::Plugin::AfterCheck}, // new ConvertPrimitiveCastMethodCall, + new FieldOveriding, new AnnotationCopyPostLowering, new AsyncMethodLowering, new DeclareOverloadLowering, diff --git a/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt b/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt index 89d140de9fba3fd0ec99e838748ce8c04f672cdd..0b81bcc2d9724f633bb84610391406ef7e972fbc 100644 --- a/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt +++ b/ets2panda/test/compiler/ets/invalidInheritance3-expected.txt @@ -1033,3 +1033,4 @@ } } } +TypeError: Overriding field has a different type, overriding field: 'a' overriden field 'a' [invalidInheritance3.ets:21:5]