diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index 8992e4463266032d54ade146aee52b4d6f28f6a7..0f16a31076e672bc9b6d67b2a749ed9b2fd3697a 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -711,6 +711,14 @@ void ETSCompiler::CompileAny(const ir::CallExpression *expr, const ir::Expressio etsg->EmitAnyCheckCast(expr, returnType); } +static const checker::Type *GetAssemblerLUB(const checker::Type *t) +{ + if (t->IsETSUnionType()) { + return t->AsETSUnionType()->GetAssemblerLUB(); + } + return t; +} + void ETSCompiler::EmitCall(const ir::CallExpression *expr, compiler::VReg &calleeReg, checker::Signature *signature) const { @@ -730,12 +738,12 @@ void ETSCompiler::EmitCall(const ir::CallExpression *expr, compiler::VReg &calle } else if (expr->Callee()->IsMemberExpression()) { auto me = expr->Callee()->AsMemberExpression(); auto obj = me->Object(); + auto *objType = GetAssemblerLUB(etsg->Checker()->GetApparentType(me->Object()->TsType())); if (obj->IsSuperExpression()) { etsg->CallExact(expr, signature, calleeReg, expr->Arguments()); // NOTE: need to refactor: type of member expression object can be obtained via // me->ObjType() or me->Object()->TsType() and they may differ!!!! - } else if (me->ObjType() == etsg->Checker()->GlobalETSObjectType() && - (etsg->Checker()->GetApparentType(me->Object()->TsType())->IsETSUnionType())) { + } else if (me->ObjType() == etsg->Checker()->GlobalETSObjectType() && objType->IsETSUnionType()) { etsg->CallByName(expr, signature, calleeReg, expr->Arguments()); } else { etsg->CallVirtual(expr, signature, calleeReg, expr->Arguments()); @@ -926,7 +934,7 @@ void ETSCompiler::Compile(const ir::MemberExpression *expr) const return; } - auto *const objectType = etsg->Checker()->GetApparentType(expr->Object()->TsType()); + auto *objectType = GetAssemblerLUB(etsg->Checker()->GetApparentType(expr->Object()->TsType())); auto &propName = expr->Property()->AsIdentifier()->Name(); auto ottctx = compiler::TargetTypeContext(etsg, expr->Object()->TsType()); diff --git a/ets2panda/compiler/lowering/ets/unionLowering.cpp b/ets2panda/compiler/lowering/ets/unionLowering.cpp index 61cacd86a4682c78ed169b096ee0bc69b5ce8015..47c334d16cf3d781d4c1ec17865bec6bc9b64dac 100644 --- a/ets2panda/compiler/lowering/ets/unionLowering.cpp +++ b/ets2panda/compiler/lowering/ets/unionLowering.cpp @@ -36,7 +36,7 @@ std::string GetAccessClassName(const checker::ETSUnionType *unionType) { std::stringstream ss; ss << PREFIX; - unionType->ToString(ss, false); + ss << unionType->ToStringAsSrc(); std::string res(ss.str()); std::replace(res.begin(), res.end(), '.', '-'); std::replace(res.begin(), res.end(), '|', '_'); @@ -44,39 +44,52 @@ std::string GetAccessClassName(const checker::ETSUnionType *unionType) return res; } -static ir::ClassDefinition *GetUnionAccessClass(public_lib::Context *ctx, varbinder::VarBinder *varbinder, - std::string const &name) +static ir::ClassDefinition *GetOrCreateNamedAccessClass(public_lib::Context *ctx, varbinder::VarBinder *vbind, + const checker::ETSUnionType *unionType) { auto *checker = ctx->GetChecker()->AsETSChecker(); auto *allocator = ctx->Allocator(); + + auto name = util::UString(GetAccessClassName(unionType), allocator); + auto globScope = vbind->Program()->GlobalClassScope(); // Create the name for the synthetic class node - if (auto foundVar = checker->Scope()->FindLocal(util::StringView(name), varbinder::ResolveBindingOptions::BINDINGS); + if (auto foundVar = globScope->FindLocal(name.View(), varbinder::ResolveBindingOptions::DECLARATION); foundVar != nullptr) { return foundVar->Declaration()->Node()->AsClassDefinition(); } - util::UString unionFieldClassName(util::StringView(name), allocator); - auto *ident = ctx->AllocNode(unionFieldClassName.View(), allocator); - auto [decl, var] = varbinder->NewVarDecl(ident->Start(), ident->Name()); - ident->SetVariable(var); - auto classCtx = varbinder::LexicalScope(varbinder); - auto *classDef = ctx->AllocNode(ctx->Allocator(), ident, ir::ClassDefinitionModifiers::GLOBAL, + auto *ident = ctx->AllocNode(name.View(), allocator); + + auto *classDef = ctx->AllocNode(ctx->Allocator(), ident, ir::ClassDefinitionModifiers::CLASS_DECL, ir::ModifierFlags::ABSTRACT, Language(Language::Id::ETS)); - classDef->SetScope(classCtx.GetScope()); auto *classDecl = ctx->AllocNode(classDef, allocator); - classDef->Scope()->BindNode(classDecl->Definition()); - decl->BindNode(classDef); - var->SetScope(classDef->Scope()); - varbinder->AsETSBinder()->BuildClassDefinition(classDef); + vbind->Program()->Ast()->AddStatement(classDecl); + + { + auto clsCtx = + varbinder::LexicalScope::Enter(vbind, globScope); + CheckLoweredNode(vbind->AsETSBinder(), checker, classDecl); + } - auto globalBlock = varbinder->Program()->Ast(); - classDecl->SetParent(globalBlock); - globalBlock->AddStatement(classDecl); - classDecl->Check(checker); return classDef; } +static varbinder::Variable *CheckIfPropertyExists(const checker::Type *t, util::StringView name) +{ + if (t == nullptr || !t->IsETSObjectType()) { + return nullptr; + } + + const auto *scope = t->AsETSObjectType()->GetDeclNode()->Scope(); + + if (auto foundVar = scope->FindLocal(name, varbinder::ResolveBindingOptions::ALL_NON_STATIC); + foundVar != nullptr) { + return foundVar; + } + return nullptr; +} + static std::tuple CreateNamedAccessMethod( public_lib::Context *ctx, varbinder::VarBinder *varbinder, ir::MemberExpression *expr, checker::Signature *signature) @@ -84,9 +97,15 @@ static std::tuple CreateNamedA auto *allocator = ctx->Allocator(); auto *checker = ctx->GetChecker()->AsETSChecker(); + auto methodName = expr->Property()->AsIdentifier()->Name(); auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); - auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType)); - auto methodName = expr->TsType()->AsETSFunctionType()->Name(); + + auto accessClass = GetOrCreateNamedAccessClass(ctx, varbinder, unionType); + auto accessClassScope = accessClass->Scope()->AsClassScope(); + + if (auto var = CheckIfPropertyExists(accessClass->TsType(), methodName); var != nullptr) { + return {var->AsLocalVariable(), var->TsType()->AsETSFunctionType()->CallSignatures().front()}; + } // Create method name for synthetic class auto *methodIdent = ctx->AllocNode(methodName, allocator); @@ -114,12 +133,12 @@ static std::tuple CreateNamedA ir::ModifierFlags::PUBLIC | ir::ModifierFlags::ABSTRACT, allocator, false); ArenaVector methodDecl {allocator->Adapter()}; methodDecl.push_back(method); - accessClass->AddProperties(std::move(methodDecl)); + accessClassScope->Node()->AsClassDefinition()->AddProperties(std::move(methodDecl)); { auto clsCtx = - varbinder::LexicalScope::Enter(varbinder, accessClass->Scope()->AsClassScope()); - auto boundCtx = varbinder::BoundContext(varbinder->AsETSBinder()->GetRecordTable(), accessClass, true); + varbinder::LexicalScope::Enter(varbinder, accessClassScope); + // auto boundCtx = varbinder::BoundContext(varbinder->AsETSBinder()->GetRecordTable(), accessClass, true); CheckLoweredNode(varbinder->AsETSBinder(), checker, method); } @@ -127,53 +146,65 @@ static std::tuple CreateNamedA method->TsType()->AsETSFunctionType()->CallSignatures().front()}; } -static varbinder::LocalVariable *CreateNamedAccessProperty(public_lib::Context *ctx, varbinder::VarBinder *varbinder, +static varbinder::LocalVariable *CreateNamedAccessField(public_lib::Context *ctx, varbinder::VarBinder *varbinder, ir::MemberExpression *expr) { auto *const allocator = ctx->Allocator(); auto *checker = ctx->GetChecker()->AsETSChecker(); + auto fieldName = expr->Property()->AsIdentifier()->Name(); auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); - auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType)); - auto propName = expr->Property()->AsIdentifier()->Name(); + + auto accessClass = GetOrCreateNamedAccessClass(ctx, varbinder, unionType); + auto accessClassScope = accessClass->Scope()->AsClassScope(); + + if (auto var = CheckIfPropertyExists(accessClass->TsType(), fieldName); var != nullptr) { + return var->AsLocalVariable(); + } + auto fieldType = expr->TsType(); auto uncheckedType = expr->UncheckedType(); auto *typeToSet = uncheckedType == nullptr ? fieldType : uncheckedType; + auto typeAnno = ctx->AllocNode(typeToSet, allocator); // Create field name for synthetic class - auto *fieldIdent = ctx->AllocNode(propName, allocator); + auto *fieldIdent = ctx->AllocNode(fieldName, allocator); // Create the synthetic class property node auto *field = - ctx->AllocNode(fieldIdent, nullptr, nullptr, ir::ModifierFlags::NONE, allocator, false); - - // Add the declaration to the scope - auto [decl, var] = varbinder->NewVarDecl(fieldIdent->Start(), fieldIdent->Name()); - var->AddFlag(varbinder::VariableFlags::PROPERTY); - var->SetTsType(typeToSet); - fieldIdent->SetVariable(var); - field->SetTsType(typeToSet); - decl->BindNode(field); + ctx->AllocNode(fieldIdent, nullptr, typeAnno, ir::ModifierFlags::NONE, allocator, false); ArenaVector fieldDecl {allocator->Adapter()}; fieldDecl.push_back(field); accessClass->AddProperties(std::move(fieldDecl)); - return var->AsLocalVariable(); + + { + auto clsCtx = + varbinder::LexicalScope::Enter(varbinder, accessClassScope); + CheckLoweredNode(varbinder->AsETSBinder(), checker, field); + } + + return field->Key()->Variable()->AsLocalVariable(); } static varbinder::LocalVariable *CreateNamedAccess(public_lib::Context *ctx, varbinder::VarBinder *varbinder, ir::MemberExpression *expr) { auto type = expr->TsType(); - auto name = expr->Property()->AsIdentifier()->Name(); + auto propName = expr->Property()->AsIdentifier()->Name(); auto *checker = ctx->GetChecker()->AsETSChecker(); auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); - auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType)); - auto *classScope = accessClass->Scope()->AsClassScope(); - if (auto *var = classScope->FindLocal(name, varbinder::ResolveBindingOptions::ALL_NON_STATIC); var != nullptr) { - return var->AsLocalVariable(); + if (auto found = CheckIfPropertyExists(unionType->GetAssemblerLUB(), propName); found != nullptr) { + auto foundType = found->TsType(); + if (foundType->IsETSMethodType() && !foundType->IsETSArrowType()) { + auto parent = expr->Parent()->AsCallExpression(); + if (parent->Callee() == expr) { + parent->SetSignature(foundType->AsETSFunctionType()->CallSignatures().front()); + } + } + return found->AsLocalVariable(); } // arrow type fields should be processed as property access not method invocation @@ -186,9 +217,7 @@ static varbinder::LocalVariable *CreateNamedAccess(public_lib::Context *ctx, var return var; } - // Enter the union filed class instance field scope - auto fieldCtx = varbinder::LexicalScope::Enter(varbinder, classScope->InstanceFieldScope()); - return CreateNamedAccessProperty(ctx, varbinder, expr); + return CreateNamedAccessField(ctx, varbinder, expr); } static void HandleUnionPropertyAccess(public_lib::Context *ctx, varbinder::VarBinder *vbind, ir::MemberExpression *expr) diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index 7694d4828f5fd03d527d30014b265fec919db64d..2684371a51ce173344a26d8aabe2da87e84ce203 100644 --- a/ets2panda/test/CMakeLists.txt +++ b/ets2panda/test/CMakeLists.txt @@ -73,6 +73,7 @@ function(ets2panda_add_gtest TARGET) ${PANDA_SANITIZERS_LIST} ${ARG_UNPARSED_ARGUMENTS} ) + panda_target_compile_definitions(${TARGET} PRIVATE -DBINARY_ROOT="${CMAKE_BINARY_DIR}/bin") endfunction(ets2panda_add_gtest) diff --git a/ets2panda/test/unit/CMakeLists.txt b/ets2panda/test/unit/CMakeLists.txt index 78467d144993ce0601bc57922ef60949dc454cb1..e4719f99f1d5fbe44fc4b3dada6fb1d50016560f 100644 --- a/ets2panda/test/unit/CMakeLists.txt +++ b/ets2panda/test/unit/CMakeLists.txt @@ -67,3 +67,7 @@ add_subdirectory(ets_specific_optimizer) ets2panda_add_gtest(es2panda_checker_tests CPP_SOURCES checker_test.cpp ) + +ets2panda_add_gtest(es2panda_union_common_property_access_test + CPP_SOURCES union_common_property_access.cpp +) diff --git a/ets2panda/test/unit/union_common_property_access.cpp b/ets2panda/test/unit/union_common_property_access.cpp new file mode 100644 index 0000000000000000000000000000000000000000..128201979f7e68d22e7f094697e5c2ace560d43d --- /dev/null +++ b/ets2panda/test/unit/union_common_property_access.cpp @@ -0,0 +1,145 @@ +/** + * 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 "test/utils/asm_test.h" + +namespace ark::es2panda::compiler::test { + +class UnionCommonPropertyAccessTest : public ::test::utils::AsmTest { +protected: + void AssertFunctionHasInst(pandasm::Function *func, pandasm::Opcode opcode) + { + auto &ins = func->ins; + auto found = std::find_if(ins.begin(), ins.end(), [opcode](pandasm::Ins &i) { return i.opcode == opcode; }); + ASSERT_NE(found, ins.end()); + } +}; + +TEST_F(UnionCommonPropertyAccessTest, CommonBaseClass1) +{ + std::string_view src = R"( + class A { + meth(): number { return 1 } + fld: number = 2 + } + + class B1 extends A { + meth(): number { return 3 } + fld: number = 4 + } + + class B2 extends A { + meth(): number { return 5 } + fld: number = 6 + } + + function foo(a: B1|B2) { + return a.fld + a.meth(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:A;f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + AssertFunctionHasInst(func, pandasm::Opcode::LDOBJ_64); + AssertFunctionHasInst(func, pandasm::Opcode::CALL_VIRT_SHORT); +} + +TEST_F(UnionCommonPropertyAccessTest, CommonBaseClass2) +{ + std::string_view src = R"( + class A { + meth(): number { return 1 } + fld: number = 2 + } + + class B extends A { + meth(): number { return 3 } + fld: number = 4 + } + + function foo(a: B|B) { + return a.fld + a.meth(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:B;f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + AssertFunctionHasInst(func, pandasm::Opcode::LDOBJ_64); + AssertFunctionHasInst(func, pandasm::Opcode::CALL_VIRT_SHORT); +} + +TEST_F(UnionCommonPropertyAccessTest, CommonBaseClass3) +{ + std::string_view src = R"( + class A { + meth(): number { return 1 } + fld: number = 2 + } + + class B1 extends A { + meth(): number { return 3 } + fld: number = 4 + } + + class B2 extends A { + meth(): number { return 5 } + fld: number = 6 + } + + function foo(a: B1|B2) { + return a.fld + a.meth(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:std.core.Object;f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + AssertFunctionHasInst(func, pandasm::Opcode::ETS_LDOBJ_NAME_64); + AssertFunctionHasInst(func, pandasm::Opcode::ETS_CALL_NAME_SHORT); +} + +TEST_F(UnionCommonPropertyAccessTest, CommonBaseClass4) +{ + std::string_view src = R"( + + class A {} + class B {} + + function foo(a: A|B) { + return a.toString(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:std.core.Object;std.core.String;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + AssertFunctionHasInst(func, pandasm::Opcode::CALL_VIRT_SHORT); +} + +} // namespace ark::es2panda::compiler::test diff --git a/ets2panda/test/utils/asm_test.cpp b/ets2panda/test/utils/asm_test.cpp index 1f8e1c6d59eeda4e7e5b64eae587cccaa79588d5..7e96bee54758e960fb611b86037603ad8b2414b5 100644 --- a/ets2panda/test/utils/asm_test.cpp +++ b/ets2panda/test/utils/asm_test.cpp @@ -289,7 +289,7 @@ void AsmTest::CheckClassFieldWithoutAnnotations(ark::pandasm::Program *program, void AsmTest::SetCurrentProgram(std::string_view src) { int argc = 1; - const char *argv = "../../../../bin/es2panda"; // NOLINT(modernize-avoid-c-arrays) + const char *argv = BINARY_ROOT "/es2panda"; // NOLINT(modernize-avoid-c-arrays) static constexpr std::string_view FILE_NAME = "dummy.ets"; program_ = GetProgram(argc, &argv, FILE_NAME, src); @@ -299,8 +299,7 @@ void AsmTest::SetCurrentProgram(std::string_view src) std::unique_ptr AsmTest::GetCurrentProgram(std::string_view src) { static constexpr std::string_view FILE_NAME = "dummy.ets"; - std::array args = {"../../../../../bin/es2panda", - "--ets-unnamed"}; // NOLINT(modernize-avoid-c-arrays) + std::array args = {BINARY_ROOT "/es2panda", "--ets-unnamed"}; // NOLINT(modernize-avoid-c-arrays) auto program = GetProgram(args.size(), args.data(), FILE_NAME, src); return program;