diff --git a/ets2panda/compiler/lowering/ets/localClassLowering.cpp b/ets2panda/compiler/lowering/ets/localClassLowering.cpp index febb95ab47b37460f58f61183d64b182f764a3a8..43de016f6473d1d1b0ca3b0366449165f7425e9a 100644 --- a/ets2panda/compiler/lowering/ets/localClassLowering.cpp +++ b/ets2panda/compiler/lowering/ets/localClassLowering.cpp @@ -25,6 +25,35 @@ std::string_view LocalClassConstructionPhase::Name() const return "LocalClassConstructionPhase"; } +struct LocalClassConstructionInfo { +public: + explicit LocalClassConstructionInfo(public_lib::Context *context) + : ctx_(context), capturedVarsMap_(context->allocator->Adapter()), parameterMap_(context->allocator->Adapter()) + { + } + + public_lib::Context *Ctx() + { + return ctx_; + } + + ArenaUnorderedMap> &CapturedVarsMap() + { + return capturedVarsMap_; + } + + ArenaMap &ParameterMap() + { + return parameterMap_; + } + +private: + public_lib::Context *ctx_; + ArenaUnorderedMap> capturedVarsMap_; + // Map the captured variable to the constructor parameter + ArenaMap parameterMap_; +}; + static ir::ClassProperty *CreateCapturedField(public_lib::Context *ctx, const varbinder::Variable *capturedVar, varbinder::ClassScope *scope, size_t &idx) { @@ -57,7 +86,8 @@ static ir::ClassProperty *CreateCapturedField(public_lib::Context *ctx, const va return field; } -static ir::Statement *CreateCtorFieldInit(public_lib::Context *ctx, util::StringView name, varbinder::Variable *var) +static ir::Statement *CreateCtorFieldInit(public_lib::Context *ctx, util::StringView lhs, util::StringView rhs, + varbinder::Variable *var) { // Create synthetic field initializers for the local class fields // The node structure is the following: this.field0 = field0, where the left hand side refers to the local @@ -66,10 +96,10 @@ static ir::Statement *CreateCtorFieldInit(public_lib::Context *ctx, util::String auto *allocator = ctx->Allocator(); auto *thisExpr = allocator->New(); - auto *fieldAccessExpr = allocator->New(name, allocator); + auto *fieldAccessExpr = allocator->New(lhs, allocator); auto *leftHandSide = util::NodeAllocator::ForceSetParent( allocator, thisExpr, fieldAccessExpr, ir::MemberExpressionKind::PROPERTY_ACCESS, false, false); - auto *rightHandSide = allocator->New(name, allocator); + auto *rightHandSide = allocator->New(rhs, allocator); rightHandSide->SetVariable(var); auto *initializer = util::NodeAllocator::ForceSetParent( allocator, leftHandSide, rightHandSide, lexer::TokenType::PUNCTUATOR_SUBSTITUTION); @@ -77,7 +107,7 @@ static ir::Statement *CreateCtorFieldInit(public_lib::Context *ctx, util::String return util::NodeAllocator::ForceSetParent(allocator, initializer); } -void LocalClassConstructionPhase::CreateClassPropertiesForCapturedVariables( +static void CreateClassPropertiesForCapturedVariables( public_lib::Context *ctx, ir::ClassDefinition *classDef, ArenaSet const &capturedVars, ArenaMap &variableMap, ArenaMap &propertyMap) @@ -99,9 +129,8 @@ void LocalClassConstructionPhase::CreateClassPropertiesForCapturedVariables( classDef->AddProperties(std::move(properties)); } -ir::ETSParameterExpression *LocalClassConstructionPhase::CreateParam(public_lib::Context *ctx, - varbinder::FunctionParamScope *scope, - util::StringView name, checker::Type *type) +static ir::ETSParameterExpression *CreateParam(public_lib::Context *ctx, varbinder::FunctionParamScope *scope, + util::StringView name, checker::Type *type) { auto *checker = ctx->GetChecker()->AsETSChecker(); auto newParam = checker->AddParam(name, nullptr); @@ -115,24 +144,46 @@ ir::ETSParameterExpression *LocalClassConstructionPhase::CreateParam(public_lib: return newParam; } -void LocalClassConstructionPhase::ModifyConstructorParameters( - public_lib::Context *ctx, ir::ClassDefinition *classDef, ArenaSet const &capturedVars, - ArenaMap &variableMap, - ArenaMap ¶meterMap) +static ArenaSet CollectSuperCapturedVars(LocalClassConstructionInfo &maps, + ir::ClassDefinition *classDef) +{ + ArenaSet allCapturedVars {maps.Ctx()->allocator->Adapter()}; + + auto *superClassExpr = classDef->Super(); + while (superClassExpr != nullptr && superClassExpr->TsType()->IsETSObjectType()) { + auto *superDecl = superClassExpr->TsType()->AsETSObjectType()->GetDeclNode(); + if (superDecl != nullptr && superDecl->IsClassDefinition()) { + auto *superClassDef = superDecl->AsClassDefinition(); + auto it = maps.CapturedVarsMap().find(superClassDef); + if (it != maps.CapturedVarsMap().end()) { + ArenaSet superCapturedVars = it->second; + allCapturedVars.insert(superCapturedVars.begin(), superCapturedVars.end()); + } + superClassExpr = superClassDef->Super(); + } else { + break; + } + } + return allCapturedVars; +} +static void ModifyConstructorParameters(LocalClassConstructionInfo &maps, ir::ClassDefinition *classDef, + ArenaSet const &capturedVars, + ArenaMap &variableMap) { auto *classType = classDef->TsType()->AsETSObjectType(); + ArenaSet allCapturedVars = CollectSuperCapturedVars(maps, classDef); + allCapturedVars.insert(capturedVars.begin(), capturedVars.end()); for (auto *signature : classType->ConstructSignatures()) { - LOG(DEBUG, ES2PANDA) << " - Modifying Constructor: " << signature->InternalName(); auto constructor = signature->Function(); auto &sigParams = signature->Params(); auto ¶meters = constructor->ParamsForUpdate(); - signature->GetSignatureInfo()->minArgCount += capturedVars.size(); + signature->GetSignatureInfo()->minArgCount += allCapturedVars.size(); ES2PANDA_ASSERT(signature == constructor->Signature()); - for (auto var : capturedVars) { - auto *newParam = CreateParam(ctx, constructor->Scope()->ParamScope(), var->Name(), var->TsType()); + for (auto var : allCapturedVars) { + auto *newParam = CreateParam(maps.Ctx(), constructor->Scope()->ParamScope(), var->Name(), var->TsType()); newParam->SetParent(constructor); // NOTE(psiket) : Moving the parameter after the 'this'. Should modify the AddParam // to be able to insert after the this. @@ -144,19 +195,19 @@ void LocalClassConstructionPhase::ModifyConstructorParameters( parameters.insert(parameters.begin(), newParam); ES2PANDA_ASSERT(newParam->Variable()->Type() == varbinder::VariableType::LOCAL); sigParams.insert(sigParams.begin(), newParam->Ident()->Variable()->AsLocalVariable()); - parameterMap[var] = newParam->Ident()->Variable()->AsLocalVariable(); + maps.ParameterMap()[var] = newParam->Ident()->Variable()->AsLocalVariable(); } - reinterpret_cast(ctx->GetChecker()->AsETSChecker()->VarBinder()) + reinterpret_cast(maps.Ctx()->GetChecker()->AsETSChecker()->VarBinder()) ->BuildFunctionName(constructor); - LOG(DEBUG, ES2PANDA) << " Transformed Constructor: " << signature->InternalName(); auto *body = constructor->Body(); - ArenaVector initStatements(ctx->allocator->Adapter()); + ArenaVector initStatements(maps.Ctx()->allocator->Adapter()); for (auto var : capturedVars) { auto *propertyVar = variableMap[var]; - auto *initStatement = CreateCtorFieldInit(ctx, propertyVar->Name(), propertyVar); + auto *ctorParamVar = maps.ParameterMap()[var]; + auto *initStatement = + CreateCtorFieldInit(maps.Ctx(), propertyVar->Name(), ctorParamVar->Name(), propertyVar); auto *fieldInit = initStatement->AsExpressionStatement()->GetExpression()->AsAssignmentExpression(); - auto *ctorParamVar = parameterMap[var]; auto *fieldVar = variableMap[var]; auto *leftHandSide = fieldInit->Left(); leftHandSide->AsMemberExpression()->SetObjectType(classType); @@ -176,7 +227,7 @@ void LocalClassConstructionPhase::ModifyConstructorParameters( } } -void LocalClassConstructionPhase::RemapReferencesFromCapturedVariablesToClassProperties( +static void RemapReferencesFromCapturedVariablesToClassProperties( ir::ClassDefinition *classDef, ArenaMap &variableMap) { auto *classType = classDef->TsType()->AsETSObjectType(); @@ -213,57 +264,88 @@ void LocalClassConstructionPhase::RemapReferencesFromCapturedVariablesToClassPro } } -void LocalClassConstructionPhase::HandleLocalClass( - public_lib::Context *ctx, - ArenaUnorderedMap> &capturedVarsMap, - ir::ClassDefinition *classDef) +static void HandleLocalClass(LocalClassConstructionInfo &maps, ir::ClassDefinition *classDef) { LOG(DEBUG, ES2PANDA) << "Altering local class with the captured variables: " << classDef->InternalName(); - auto capturedVars = FindCaptured(ctx->allocator, classDef); + auto capturedVars = FindCaptured(maps.Ctx()->allocator, classDef); + // Map the captured variable to the variable of the class property - ArenaMap variableMap(ctx->allocator->Adapter()); + ArenaMap variableMap(maps.Ctx()->allocator->Adapter()); // Map the captured variable to the class property - ArenaMap propertyMap(ctx->allocator->Adapter()); - // Map the captured variable to the constructor parameter - ArenaMap parameterMap(ctx->allocator->Adapter()); + ArenaMap propertyMap(maps.Ctx()->allocator->Adapter()); - CreateClassPropertiesForCapturedVariables(ctx, classDef, capturedVars, variableMap, propertyMap); - ModifyConstructorParameters(ctx, classDef, capturedVars, variableMap, parameterMap); + CreateClassPropertiesForCapturedVariables(maps.Ctx(), classDef, capturedVars, variableMap, propertyMap); + ModifyConstructorParameters(maps, classDef, capturedVars, variableMap); RemapReferencesFromCapturedVariablesToClassProperties(classDef, variableMap); - capturedVarsMap.emplace(classDef, std::move(capturedVars)); + maps.CapturedVarsMap().emplace(classDef, std::move(capturedVars)); +} + +static void HandleLocalClassInstantiation(LocalClassConstructionInfo &maps, ir::ClassDefinition *classDef, + ir::ETSNewClassInstanceExpression *newExpr) +{ + LOG(DEBUG, ES2PANDA) << "Instantiating local class: " << classDef->Ident()->Name(); + auto capturedVarsIt = maps.CapturedVarsMap().find(classDef); + ES2PANDA_ASSERT(capturedVarsIt != maps.CapturedVarsMap().cend()); + auto &capturedVars = capturedVarsIt->second; + ArenaSet allCapturedVars = CollectSuperCapturedVars(maps, classDef); + allCapturedVars.insert(capturedVars.begin(), capturedVars.end()); + for (auto *var : allCapturedVars) { + LOG(DEBUG, ES2PANDA) << " - Extending constructor argument with captured variable: " << var->Name(); + + auto *param = maps.Ctx()->AllocNode(var->Name(), maps.Ctx()->allocator); + param->SetVariable(var); + param->SetIgnoreBox(); + param->SetTsType(param->Variable()->TsType()); + param->SetParent(newExpr); + newExpr->AddToArgumentsFront(param); + } +} + +static void HandleSuperCallExpression(public_lib::Context *ctx, ArenaSet ¶metersForCaptures, + ir::CallExpression *callExpr) +{ + ir::AstNode *iter = callExpr; + while (iter != nullptr && iter->Parent() != nullptr) { + iter = iter->Parent(); + if (iter->IsScriptFunction()) { + break; + } + } + if (iter == nullptr || !iter->IsScriptFunction()) { + return; + } + auto *outerFunction = iter->AsScriptFunction(); + if (!outerFunction->IsConstructor()) { + return; + } + for (auto *param : outerFunction->Params()) { + if (!param->IsETSParameterExpression()) { + continue; + } + auto *var = param->AsETSParameterExpression()->Ident()->Variable(); + if (parametersForCaptures.count(var) > 0) { + auto *p = ctx->AllocNode(var->Name(), ctx->allocator); + p->SetVariable(var); + p->SetIgnoreBox(); + p->SetTsType(p->Variable()->TsType()); + p->SetParent(callExpr); + callExpr->Arguments().insert(callExpr->Arguments().begin(), p); + } + } } bool LocalClassConstructionPhase::PerformForModule(public_lib::Context *ctx, parser::Program *program) { checker::ETSChecker *const checker = ctx->GetChecker()->AsETSChecker(); - ArenaUnorderedMap> capturedVarsMap { - ctx->allocator->Adapter()}; + LocalClassConstructionInfo maps {ctx}; program->Ast()->IterateRecursivelyPostorder([&](ir::AstNode *ast) { if (ast->IsClassDefinition() && ast->AsClassDefinition()->IsLocal()) { - HandleLocalClass(ctx, capturedVarsMap, ast->AsClassDefinition()); + HandleLocalClass(maps, ast->AsClassDefinition()); } }); // Alter the instantiations - auto handleLocalClassInstantiation = [ctx, &capturedVarsMap](ir::ClassDefinition *classDef, - ir::ETSNewClassInstanceExpression *newExpr) { - LOG(DEBUG, ES2PANDA) << "Instantiating local class: " << classDef->Ident()->Name(); - auto capturedVarsIt = capturedVarsMap.find(classDef); - ES2PANDA_ASSERT(capturedVarsIt != capturedVarsMap.cend()); - auto &capturedVars = capturedVarsIt->second; - for (auto *var : capturedVars) { - LOG(DEBUG, ES2PANDA) << " - Extending constructor argument with captured variable: " << var->Name(); - - auto *param = ctx->AllocNode(var->Name(), ctx->allocator); - param->SetVariable(var); - param->SetIgnoreBox(); - param->SetTsType(param->Variable()->TsType()); - param->SetParent(newExpr); - newExpr->AddToArgumentsFront(param); - } - }; - program->Ast()->IterateRecursivelyPostorder([&](ir::AstNode *ast) { if (ast->IsETSNewClassInstanceExpression()) { auto *newExpr = ast->AsETSNewClassInstanceExpression(); @@ -277,11 +359,32 @@ bool LocalClassConstructionPhase::PerformForModule(public_lib::Context *ctx, par auto *classDef = calleeObj->GetDeclNode()->AsClassDefinition(); if (classDef->IsLocal()) { - handleLocalClassInstantiation(classDef, newExpr); + HandleLocalClassInstantiation(maps, classDef, newExpr); } } }); - + // Alter the super call expressions + ArenaSet parametersForCaptures {ctx->allocator->Adapter()}; + for (const auto &pair : maps.ParameterMap()) { + parametersForCaptures.insert(pair.second); + } + program->Ast()->IterateRecursivelyPostorder([&](ir::AstNode *ast) { + if (ast->IsCallExpression() && ast->AsCallExpression()->Callee()->IsSuperExpression()) { + auto *superExpr = ast->AsCallExpression()->Callee()->AsSuperExpression(); + auto *calleeType = superExpr->TsType(); + if (!calleeType->IsETSObjectType()) { + return; + } + auto *superDecl = calleeType->AsETSObjectType()->GetDeclNode(); + if (!superDecl->IsClassDefinition()) { + return; + } + if (maps.CapturedVarsMap().count(superDecl->AsClassDefinition()) == 0) { + return; + } + HandleSuperCallExpression(ctx, parametersForCaptures, ast->AsCallExpression()); + } + }); return true; } diff --git a/ets2panda/compiler/lowering/ets/localClassLowering.h b/ets2panda/compiler/lowering/ets/localClassLowering.h index 08ee325919ec75916df1e57fa295a0375ba2bd75..17c7c8cc2101c23ba3fbdb45ae6791a5fe3eaa43 100644 --- a/ets2panda/compiler/lowering/ets/localClassLowering.h +++ b/ets2panda/compiler/lowering/ets/localClassLowering.h @@ -24,27 +24,6 @@ class LocalClassConstructionPhase : public PhaseForBodies { public: std::string_view Name() const override; bool PerformForModule(public_lib::Context *ctx, parser::Program *program) override; - -protected: - void CreateClassPropertiesForCapturedVariables(public_lib::Context *ctx, ir::ClassDefinition *classDef, - ArenaSet const &capturedVars, - ArenaMap &variableMap, - ArenaMap &propertyMap); - - void ModifyConstructorParameters(public_lib::Context *ctx, ir::ClassDefinition *classDef, - ArenaSet const &capturedVars, - ArenaMap &variableMap, - ArenaMap ¶meterMap); - - void RemapReferencesFromCapturedVariablesToClassProperties( - ir::ClassDefinition *classDef, ArenaMap &variableMap); - - void HandleLocalClass(public_lib::Context *ctx, - ArenaUnorderedMap> &capturedVarsMap, - ir::ClassDefinition *classDef); - - ir::ETSParameterExpression *CreateParam(public_lib::Context *ctx, varbinder::FunctionParamScope *scope, - util::StringView name, checker::Type *type); }; } // namespace ark::es2panda::compiler diff --git a/ets2panda/test/ast/compiler/ets/local_class_inheritance.ets b/ets2panda/test/ast/compiler/ets/local_class_inheritance.ets new file mode 100644 index 0000000000000000000000000000000000000000..3e46eae10e3c2a6ea78c44d32d92eaa2600d3064 --- /dev/null +++ b/ets2panda/test/ast/compiler/ets/local_class_inheritance.ets @@ -0,0 +1,30 @@ +/* + * 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 main() { + let d:int = 10; + class TestClassSuper { + constructor() { + d = d + 1; + } + } + class TestClass2 extends TestClassSuper { + constructor() { + super(); + } + } + let obj1:TestClassSuper = new TestClassSuper(); + let obj2:TestClass2 = new TestClass2(); +}