diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index 334a634c907970ab7380b3d1027494a36399aaaa..4c36a589cb249f3796bbf4d0188f77d50b463e16 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -301,7 +301,6 @@ public: } [[nodiscard]] Type *GetApparentType(Type *type); [[nodiscard]] Type const *GetApparentType(Type const *type) const; - ETSObjectType *GetClosestCommonAncestor(ETSObjectType *source, ETSObjectType *target); bool HasETSFunctionType(ir::TypeNode *typeAnnotation); void VariableTypeFromInitializer(varbinder::Variable *variable, Type *annotationType, Type *initType); diff --git a/ets2panda/checker/ets/object.cpp b/ets2panda/checker/ets/object.cpp index e42f1d760e2d23ca7e2974b775b2e9e8dc29a780..dc2ad716c30a9d3bb146481ec30810585afe2185 100644 --- a/ets2panda/checker/ets/object.cpp +++ b/ets2panda/checker/ets/object.cpp @@ -2696,29 +2696,6 @@ Type const *ETSChecker::GetApparentType(Type const *type) const return const_cast(const_cast(this)->GetApparentType(const_cast(type))); } -ETSObjectType *ETSChecker::GetClosestCommonAncestor(ETSObjectType *source, ETSObjectType *target) -{ - if (source->AsETSObjectType()->GetDeclNode() == target->AsETSObjectType()->GetDeclNode()) { - return source; - } - if (target->SuperType() == nullptr) { - return GlobalETSObjectType(); - } - - auto *targetBase = GetOriginalBaseType(target->SuperType()); - auto *targetType = targetBase == nullptr ? target->SuperType() : targetBase; - - auto *sourceBase = GetOriginalBaseType(source); - auto *sourceType = sourceBase == nullptr ? source : sourceBase; - - if (Relation()->IsSupertypeOf(targetType, sourceType)) { - // NOTE: TorokG. Extending the search to find intersection types - return targetType; - } - - return GetClosestCommonAncestor(sourceType, targetType); -} - void ETSChecker::CheckInvokeMethodsLegitimacy(ETSObjectType *const classType) { if (classType->HasObjectFlag(ETSObjectFlags::CHECKED_INVOKE_LEGITIMACY)) { diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index 9e26143b69d04766ede7873bbab22abb01dd8671..7992bdeeb5f257ae00a1b24f27cd8167adc26f4b 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -731,13 +731,13 @@ 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 = 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()) != nullptr) && - (etsg->Checker()->GetApparentType(me->Object()->TsType())->IsETSUnionType())) { + } else if (me->ObjType() == etsg->Checker()->GlobalETSObjectType() && (objType != nullptr) && + (objType->IsETSUnionType())) { etsg->CallByName(expr, signature, calleeReg, expr->Arguments()); } else { etsg->CallVirtual(expr, signature, calleeReg, expr->Arguments()); @@ -928,8 +928,7 @@ void ETSCompiler::Compile(const ir::MemberExpression *expr) const return; } - auto *const objectType = etsg->Checker()->GetApparentType(expr->Object()->TsType()); - auto &propName = expr->Property()->AsIdentifier()->Name(); + auto *objectType = etsg->Checker()->GetApparentType(expr->Object()->TsType()); auto ottctx = compiler::TargetTypeContext(etsg, expr->Object()->TsType()); etsg->CompileAndCheck(expr->Object()); @@ -951,7 +950,7 @@ void ETSCompiler::Compile(const ir::MemberExpression *expr) const } else if (objectType->IsETSUnionType()) { etsg->LoadPropertyByName(expr, objReg, checker::ETSChecker::FormNamedAccessMetadata(expr->PropVar())); } else { - const auto fullName = etsg->FormClassPropReference(objectType->AsETSObjectType(), propName); + const auto fullName = etsg->FormClassPropReference(expr->PropVar()); etsg->LoadProperty(expr, variableType, objReg, fullName); } etsg->GuardUncheckedType(expr, expr->UncheckedType(), expr->TsType()); diff --git a/ets2panda/compiler/core/ETSGen.h b/ets2panda/compiler/core/ETSGen.h index 4b306cccb2659231a1f06fa6f9dd783fe9a40d51..162cf88114614c3d8e20b3e5dc2e0281a88e72df 100644 --- a/ets2panda/compiler/core/ETSGen.h +++ b/ets2panda/compiler/core/ETSGen.h @@ -64,6 +64,7 @@ public: void StoreStaticOwnProperty(const ir::AstNode *node, const checker::Type *propType, const util::StringView &name); [[nodiscard]] util::StringView FormClassPropReference(const checker::ETSObjectType *classType, const util::StringView &name); + util::StringView FormClassPropReference(varbinder::Variable const *var); void StoreProperty(const ir::AstNode *node, const checker::Type *propType, VReg objReg, const util::StringView &name); @@ -485,7 +486,6 @@ private: void StringBuilder(const ir::Expression *left, const ir::Expression *right, VReg builder); void AppendTemplateString(const ir::TemplateLiteral *node); void ConcatTemplateString(const ir::TemplateLiteral *node); - util::StringView FormClassPropReference(varbinder::Variable const *var); void UnaryMinus(const ir::AstNode *node); void UnaryTilde(const ir::AstNode *node); diff --git a/ets2panda/compiler/lowering/ets/unionLowering.cpp b/ets2panda/compiler/lowering/ets/unionLowering.cpp index 2b2bc2713fdbed47e05eae8118dd52a10b1ab2ee..a398e5ad11e9c1222bf091c1dbaf8ae2066652a3 100644 --- a/ets2panda/compiler/lowering/ets/unionLowering.cpp +++ b/ets2panda/compiler/lowering/ets/unionLowering.cpp @@ -13,9 +13,7 @@ * limitations under the License. */ -#include #include "unionLowering.h" -#include "compiler/lowering/scopesInit/scopesInitPhase.h" #include "compiler/lowering/util.h" #include "varbinder/ETSBinder.h" #include "checker/ETSchecker.h" @@ -23,70 +21,177 @@ namespace ark::es2panda::compiler { -static void ReplaceAll(std::string &str, std::string_view substr, std::string_view replacement) +using UnionLubMap = std::unordered_map; + +static void TypeToString(std::stringstream &ss, const checker::Type *t); + +static void TypeParamsToString(std::stringstream &ss, const checker::Type *t) +{ + auto &typeParams = t->AsETSObjectType()->TypeArguments(); + + if (typeParams.empty()) { + return; + } + + ss << "$"; + for (auto it = typeParams.begin(); it != typeParams.end(); it++) { + TypeToString(ss, *it); + if (std::next(it) != typeParams.end()) { + ss << "_"; + } + } + ss << "$"; +} + +static void TypeToString(std::stringstream &ss, const checker::Type *t) { - for (size_t pos = str.find(substr, 0); pos != std::string::npos; pos = str.find(substr, pos)) { - str.replace(pos, substr.size(), replacement); - pos += replacement.size(); + if (t->IsETSObjectType() && t->AsETSObjectType()->HasObjectFlag(checker::ETSObjectFlags::BUILTIN_TYPE)) { + ss << t->AsETSObjectType()->Name(); + } else { + t->ToAssemblerType(ss); } + TypeParamsToString(ss, t); } std::string GetAccessClassName(const checker::ETSUnionType *unionType) { std::stringstream ss; - unionType->ToString(ss, false); - std::string newName = util::NameMangler::GetInstance()->CreateMangledNameForUnionProperty(ss.str()); - ReplaceAll(newName, "[]", "[$]$"); - return newName; + auto &constituentTypes = unionType->ConstituentTypes(); + for (auto ct = constituentTypes.begin(); ct != constituentTypes.end(); ct++) { + TypeToString(ss, *ct); + if (std::next(ct) != constituentTypes.end()) { + ss << "_"; + } + } + return util::NameMangler::GetInstance()->CreateMangledNameForUnionProperty(ss.str()); } -static ir::ClassDefinition *GetUnionAccessClass(public_lib::Context *ctx, varbinder::VarBinder *varbinder, - std::string const &name) +static checker::ETSObjectType *GetClosestCommonAncestor(checker::ETSChecker *checker, checker::ETSObjectType *source, + checker::ETSObjectType *target) +{ + if (source->AsETSObjectType()->GetDeclNode() == target->AsETSObjectType()->GetDeclNode()) { + return source; + } + if (target->SuperType() == nullptr) { + return checker->GlobalETSObjectType(); + } + + auto *targetBase = checker->GetOriginalBaseType(target->SuperType()); + auto *targetType = targetBase == nullptr ? target->SuperType() : targetBase; + + auto *sourceBase = checker->GetOriginalBaseType(source); + auto *sourceType = sourceBase == nullptr ? source : sourceBase; + + if (checker->Relation()->IsSupertypeOf(targetType, sourceType)) { + return targetType; + } + + return GetClosestCommonAncestor(checker, sourceType, targetType); +} + +static const checker::Type *GetUnionLUBType(UnionLubMap &unionLUBs, const checker::ETSUnionType *ut, + checker::ETSChecker *checker) +{ + if (auto found = unionLUBs.find(ut->GetAssemblerType()); found != unionLUBs.end()) { + return found->second; + } + + auto *apparent = checker->GetApparentType(ut); + ES2PANDA_ASSERT(apparent != nullptr); + if (!apparent->IsETSUnionType()) { + return apparent; + } + if (apparent != ut) { + return GetUnionLUBType(unionLUBs, apparent->AsETSUnionType(), checker); + } + ut = apparent->AsETSUnionType(); + + checker::Type *lub = nullptr; + for (auto *t : ut->ConstituentTypes()) { + if (t->IsTypeError()) { + return checker->GlobalTypeError(); + } + ES2PANDA_ASSERT(t->IsETSReferenceType() || t->IsETSVoidType()); + t = t->IsETSVoidType() ? checker->GlobalETSUndefinedType() : t; + + if (lub == nullptr || lub->IsETSUndefinedType()) { + lub = t; + continue; + } + if (lub == t || t->IsETSUndefinedType()) { + continue; + } + if (t->IsETSNullType()) { + return checker->GetGlobalTypesHolder()->GlobalETSObjectType(); + } + if (t->IsETSObjectType() && lub->IsETSObjectType()) { + lub = GetClosestCommonAncestor(checker, lub->AsETSObjectType(), t->AsETSObjectType()); + } else { + return checker->GetGlobalTypesHolder()->GlobalETSObjectType(); + } + } + auto res = checker->GetNonConstantType(lub); + unionLUBs.emplace(ut->GetAssemblerType(), res); + return res; +} + +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 className = 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(className.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()); - ES2PANDA_ASSERT(ident != nullptr); - ident->SetVariable(var); - - auto classCtx = varbinder::LexicalScope(varbinder); - auto *classDef = ctx->AllocNode(ctx->Allocator(), ident, ir::ClassDefinitionModifiers::GLOBAL, - ir::ModifierFlags::ABSTRACT, Language(Language::Id::ETS)); - ES2PANDA_ASSERT(classDef != nullptr); - classDef->SetScope(classCtx.GetScope()); + + auto *ident = ctx->AllocNode(className.View(), allocator); + + auto *classDef = + ctx->AllocNode(ctx->Allocator(), ident, ir::ClassDefinitionModifiers::CLASS_DECL, + ir::ModifierFlags::ABSTRACT, Language(Language::Id::ETS)); auto *classDecl = ctx->AllocNode(classDef, allocator); - ES2PANDA_ASSERT(classDecl != nullptr); - 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 std::tuple CreateNamedAccessMethod( - public_lib::Context *ctx, varbinder::VarBinder *varbinder, ir::MemberExpression *expr, - checker::Signature *signature) +static varbinder::LocalVariable *CheckIfPropertyExists(const checker::Type *t, util::StringView name) +{ + if (t == nullptr || !t->IsETSObjectType()) { + return nullptr; + } + + return t->AsETSObjectType()->GetProperty( + name, checker::PropertySearchFlags::SEARCH_INSTANCE_METHOD | + checker::PropertySearchFlags::SEARCH_INSTANCE_FIELD | checker::PropertySearchFlags::SEARCH_IN_BASE | + checker::PropertySearchFlags::DISALLOW_SYNTHETIC_METHOD_CREATION); +} + +static varbinder::LocalVariable *CreateNamedAccessMethod(public_lib::Context *ctx, varbinder::VarBinder *vbind, + ir::MemberExpression *expr, checker::Signature *signature) { auto *allocator = ctx->Allocator(); auto *checker = ctx->GetChecker()->AsETSChecker(); - auto apparentType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType())); - ES2PANDA_ASSERT(apparentType != nullptr); - auto unionType = apparentType->AsETSUnionType(); - auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType)); - auto methodName = expr->TsType()->AsETSFunctionType()->Name(); + + auto methodName = expr->Property()->AsIdentifier()->Name(); + auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); + + auto accessClass = GetOrCreateNamedAccessClass(ctx, vbind, unionType); + auto accessClassScope = accessClass->Scope()->AsClassScope(); + if (auto var = CheckIfPropertyExists(accessClass->TsType(), methodName); var != nullptr) { + return var; + } // Create method name for synthetic class auto *methodIdent = ctx->AllocNode(methodName, allocator); @@ -115,69 +220,81 @@ 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); - CheckLoweredNode(varbinder->AsETSBinder(), checker, method); - } - ES2PANDA_ASSERT(method->Id() != nullptr && method->TsType() != nullptr); - return {method->Id()->Variable()->AsLocalVariable(), - method->TsType()->AsETSFunctionType()->CallSignatures().front()}; + auto clsCtx = varbinder::LexicalScope::Enter(vbind, accessClassScope); + varbinder::BoundContext boundCtx {vbind->AsETSBinder()->GetRecordTable(), accessClass, true}; + CheckLoweredNode(vbind->AsETSBinder(), checker, method); + } + + auto res = method->Id()->Variable()->AsLocalVariable(); + accessClass->TsType()->AsETSObjectType()->AddProperty(res); + return res; } -static varbinder::LocalVariable *CreateNamedAccessProperty(public_lib::Context *ctx, varbinder::VarBinder *varbinder, - ir::MemberExpression *expr) +static varbinder::LocalVariable *CreateNamedAccessField(public_lib::Context *ctx, varbinder::VarBinder *vbind, + ir::MemberExpression *expr) { auto *const allocator = ctx->Allocator(); auto *checker = ctx->GetChecker()->AsETSChecker(); - auto apparentType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType())); - ES2PANDA_ASSERT(apparentType != nullptr); - auto unionType = apparentType->AsETSUnionType(); - auto *const accessClass = GetUnionAccessClass(ctx, varbinder, GetAccessClassName(unionType)); - auto propName = expr->Property()->AsIdentifier()->Name(); + auto fieldName = expr->Property()->AsIdentifier()->Name(); + auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); + + auto accessClass = GetOrCreateNamedAccessClass(ctx, vbind, unionType); + auto accessClassScope = accessClass->Scope()->AsClassScope(); + if (auto var = CheckIfPropertyExists(accessClass->TsType(), fieldName); var != nullptr) { + return var; + } + 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); - ES2PANDA_ASSERT(field != nullptr); - // 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(vbind, accessClassScope); + CheckLoweredNode(vbind->AsETSBinder(), checker, field); + } + + auto res = field->Key()->Variable()->AsLocalVariable(); + accessClass->TsType()->AsETSObjectType()->AddProperty(res); + return res; } -static varbinder::LocalVariable *CreateNamedAccess(public_lib::Context *ctx, varbinder::VarBinder *varbinder, +static varbinder::LocalVariable *CreateNamedAccess(UnionLubMap &unionLUBs, public_lib::Context *ctx, 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 *apparentType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType())); - ES2PANDA_ASSERT(apparentType != nullptr); - auto unionType = apparentType->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(); + auto *varbinder = checker->VarBinder(); + + auto unionType = checker->GetApparentType(checker->GetNonNullishType(expr->Object()->TsType()))->AsETSUnionType(); + if (auto found = CheckIfPropertyExists(GetUnionLUBType(unionLUBs, unionType, checker), propName); + found != nullptr) { + auto foundType = found->TsType(); + // Update signatures of call expression since current signature belongs to one of the constituent types + // of the union type + if (foundType->IsETSMethodType() && !foundType->IsETSArrowType()) { + auto parent = expr->Parent()->AsCallExpression(); + if (parent->Callee() == expr) { + parent->SetSignature(foundType->AsETSFunctionType()->CallSignatures().front()); + } + } + return found; } // arrow type fields should be processed as property access not method invocation @@ -185,38 +302,71 @@ static varbinder::LocalVariable *CreateNamedAccess(public_lib::Context *ctx, var auto parent = expr->Parent()->AsCallExpression(); ES2PANDA_ASSERT(parent->Callee() == expr && parent->Signature()->HasFunction()); - auto [var, sig] = CreateNamedAccessMethod(ctx, varbinder, expr, parent->Signature()); + auto var = CreateNamedAccessMethod(ctx, varbinder, expr, parent->Signature()); + // Update signatures of call expression since current signature belongs to one of the constituent types + // of the union type + auto sig = var->TsType()->AsETSFunctionType()->CallSignatures().front(); parent->AsCallExpression()->SetSignature(sig); 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) +static void UpdateObjectEffectiveRuntimeRepresentation(ir::MemberExpression *expr) +{ + auto propVar = expr->PropVar(); + auto containingClass = propVar->Declaration()->Node()->AsClassElement()->Parent(); + // In case of 'abstract class %%union_prop-...' + if (containingClass->IsAbstract()) { + // Nothing to update + // Object type is etsUnionType + // 'ets.call.name'/'ets.ldobj.name' will be generated + return; + } + + auto getType = [](ir::AstNode *node) { + if (node->IsTSInterfaceBody()) { + return node->Parent()->AsTSInterfaceDeclaration()->TsType()->AsETSObjectType(); + } + if (node->IsClassDefinition()) { + return node->AsClassDefinition()->TsType()->AsETSObjectType(); + } + ES2PANDA_UNREACHABLE(); + }; + + // Object type is etsObjectType + // 'call.virt'/'ldobj' will be generated + expr->Object()->SetTsType(getType(containingClass)); +} + +static void HandleUnionPropertyAccess(UnionLubMap &unionLUBs, public_lib::Context *ctx, ir::MemberExpression *expr) { if (expr->PropVar() != nullptr) { return; } - [[maybe_unused]] auto const *const parent = expr->Parent(); - expr->SetPropVar(CreateNamedAccess(ctx, vbind, expr)); - ES2PANDA_ASSERT(expr->PropVar() != nullptr); + auto var = CreateNamedAccess(unionLUBs, ctx, expr); + ES2PANDA_ASSERT(var != nullptr); + expr->SetPropVar(var); + // Update object type in order to generate optimal bytecode + UpdateObjectEffectiveRuntimeRepresentation(expr); } bool UnionLowering::PerformForModule(public_lib::Context *ctx, parser::Program *program) { + // Map union types to their LUB types + UnionLubMap unionLUBs; + checker::ETSChecker *checker = ctx->GetChecker()->AsETSChecker(); program->Ast()->TransformChildrenRecursively( - [ctx, checker](checker::AstNodePtr ast) -> checker::AstNodePtr { + [ctx, checker, &unionLUBs](checker::AstNodePtr ast) -> checker::AstNodePtr { if (ast->IsMemberExpression() && ast->AsMemberExpression()->Object()->TsType() != nullptr) { auto *objType = checker->GetApparentType(checker->GetNonNullishType(ast->AsMemberExpression()->Object()->TsType())); if (objType->IsETSUnionType()) { - HandleUnionPropertyAccess(ctx, checker->VarBinder(), ast->AsMemberExpression()); + HandleUnionPropertyAccess(unionLUBs, ctx, ast->AsMemberExpression()); return ast; } } diff --git a/ets2panda/compiler/lowering/ets/unionLowering.h b/ets2panda/compiler/lowering/ets/unionLowering.h index 0f07e014e3333d6ff43ccbcfa44f5d4c7c89e187..c5392b233ea971d0554d9abb5108bb8cb2f47d9d 100644 --- a/ets2panda/compiler/lowering/ets/unionLowering.h +++ b/ets2panda/compiler/lowering/ets/unionLowering.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 diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index 4f93b770d714d363126516a9a4235bdde2a79ae8..40a378e80ad5d411934e3e23a7729f7ca53138b8 100644 --- a/ets2panda/test/CMakeLists.txt +++ b/ets2panda/test/CMakeLists.txt @@ -75,6 +75,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 80624e7dddba2d16652860b3aa9de304712c058a..5f512926de5ba51309d99aa47186e0a3a2c24ace 100644 --- a/ets2panda/test/unit/CMakeLists.txt +++ b/ets2panda/test/unit/CMakeLists.txt @@ -75,3 +75,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..df7779dbfbcc144b952d1e8be77db3d6739509a4 --- /dev/null +++ b/ets2panda/test/unit/union_common_property_access.cpp @@ -0,0 +1,369 @@ +/** + * 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: + pandasm::Ins *FindInst(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; }); + return (found != ins.end()) ? &*(found) : nullptr; + } + + bool HasId(pandasm::Ins *ins, std::string_view sig) + { + for (size_t i = 0; i < ins->IDSize(); ++i) { + if (ins->GetID(i) == sig) { + return true; + } + } + return false; + } +}; + +TEST_F(UnionCommonPropertyAccessTest, CommonBaseClass1) +{ + 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 + } + + class C extends A { + meth(): number { return 5 } + fld: number = 6 + } + + function foo(a: B|C) { + return a.fld + a.meth(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB,C};f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::LDOBJ_64); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "A.fld")); + + auto call = FindInst(func, pandasm::Opcode::CALL_VIRT_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "A.meth:f64;")); +} + +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); + + auto ldobj = FindInst(func, pandasm::Opcode::LDOBJ_64); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "B.fld")); + + auto call = FindInst(func, pandasm::Opcode::CALL_VIRT_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "B.meth:f64;")); +} + +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 record = GetRecord("%%union_prop-B1$Int$_B2$Double$", program); + ASSERT_NE(record, nullptr); + + auto &fields = record->fieldList; + auto it = std::find_if(fields.begin(), fields.end(), [](const pandasm::Field &f) { return f.name == "fld"; }); + ASSERT_NE(it, fields.end()); + + ASSERT_NE(GetFunction("%%union_prop-B1$Int$_B2$Double$.meth:f64;", program->functionInstanceTable), nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB1,B2};f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::ETS_LDOBJ_NAME_64); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "%%union_prop-B1$Int$_B2$Double$.fld")); + + auto call = FindInst(func, pandasm::Opcode::ETS_CALL_NAME_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "%%union_prop-B1$Int$_B2$Double$.meth:f64;")); +} + +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:{UA,B};std.core.String;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto call = FindInst(func, pandasm::Opcode::CALL_VIRT_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "std.core.Object.toString:std.core.String;")); +} + +TEST_F(UnionCommonPropertyAccessTest, QualifiedClassName) +{ + std::string_view src = R"( + namespace NS { + export class A { + meth(): number { return 0 } + fld: number + } + + export class B extends A { + meth(): number { return 1 } + fld: number + } + } + + export class B extends NS.A { + meth(): number { return 3 } + fld: number + } + + function foo(a: NS.B|B) { + return a.fld + a.meth(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB,NS.B};f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::LDOBJ_64); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "NS.A.fld")); + + auto call = FindInst(func, pandasm::Opcode::CALL_VIRT_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "NS.A.meth:f64;")); +} + +TEST_F(UnionCommonPropertyAccessTest, QualifiedClassName2) +{ + std::string_view src = R"( + namespace NS { + export class A {} + export class B {} + } + + function foo(a: NS.A|NS.B) { + return a.toString(); + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UNS.A,NS.B};std.core.String;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto call = FindInst(func, pandasm::Opcode::CALL_VIRT_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "std.core.Object.toString:std.core.String;")); +} + +TEST_F(UnionCommonPropertyAccessTest, ImplementInterface) +{ + std::string_view src = R"( + namespace NS { + export interface A { + meth(): number + fld : number + } + export class B implements A { + meth(): number { return 1 } + fld: number = 2 + } + } + class B implements NS.A { + meth(): number { return 3 } + fld: number = 4 + } + + function foo(a: NS.B|B) { + return a.fld + a.meth() + } + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto record = GetRecord("%%union_prop-NS_B$Int$_B$Double$", program); + ASSERT_NE(record, nullptr); + + auto &fields = record->fieldList; + auto it = std::find_if(fields.begin(), fields.end(), [](const pandasm::Field &f) { return f.name == "fld"; }); + ASSERT_NE(it, fields.end()); + + ASSERT_NE(GetFunction("%%union_prop-NS_B$Int$_B$Double$.meth:f64;", program->functionInstanceTable), nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB,NS.B};f64;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::ETS_LDOBJ_NAME_64); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "%%union_prop-NS_B$Int$_B$Double$.fld")); + + auto call = FindInst(func, pandasm::Opcode::ETS_CALL_NAME_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "%%union_prop-NS_B$Int$_B$Double$.meth:f64;")); +} + +TEST_F(UnionCommonPropertyAccessTest, Required) +{ + std::string_view src = R"( + class Base { + a? : number + } + + namespace NS { + export class A extends Base {} + } + + class B extends Base {} + + function foo(x: Required|Required) { + return x.a.toString(); + } + + function bar(): Required { + return {a: 10} + } + + foo(bar()) + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB,NS.A};std.core.String;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::LDOBJ_OBJ); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "Base.a")); + + auto call = FindInst(func, pandasm::Opcode::CALL_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "std.core.Double.toString:std.core.String;")); +} + +TEST_F(UnionCommonPropertyAccessTest, Partial) +{ + std::string_view src = R"( + class Base { + a? : number + } + + namespace NS { + export class A extends Base {} + } + + class B extends Base {} + + function foo(x: Partial|Partial) { + return x.a!.toString(); + } + + function bar(): Partial { + return {a: 10} + } + + foo(bar()) + )"; + + auto program = GetCurrentProgram(src); + ASSERT_NE(program, nullptr); + + auto func = GetFunction("ETSGLOBAL.foo:{UB$partial,NS.A$partial};std.core.String;", program->functionStaticTable); + ASSERT_NE(func, nullptr); + + auto ldobj = FindInst(func, pandasm::Opcode::LDOBJ_OBJ); + ASSERT_NE(ldobj, nullptr); + ASSERT_TRUE(HasId(ldobj, "Base.a")); + + auto call = FindInst(func, pandasm::Opcode::CALL_SHORT); + ASSERT_NE(call, nullptr); + ASSERT_TRUE(HasId(call, "std.core.Double.toString:std.core.String;")); +} + +} // namespace ark::es2panda::compiler::test diff --git a/ets2panda/test/utils/asm_test.cpp b/ets2panda/test/utils/asm_test.cpp index 2d15e56dc65037419e3d911516a792a779064aba..2f17dcab8e4eaec506cd6a6f6c94791a00bd0a4f 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;