diff --git a/ets2panda/checker/ets/typeCreation.cpp b/ets2panda/checker/ets/typeCreation.cpp index ef795371c752c862212c7b2df72d5aec8548ace3..ca04841b30ec36fb3705526f3788a8cb759a96f0 100644 --- a/ets2panda/checker/ets/typeCreation.cpp +++ b/ets2panda/checker/ets/typeCreation.cpp @@ -14,6 +14,7 @@ */ #include "checker/ETSchecker.h" +#include "checker/ets/boxingConverter.h" #include "checker/types/ets/byteType.h" #include "checker/types/ets/charType.h" #include "checker/types/ets/etsDynamicFunctionType.h" @@ -132,16 +133,8 @@ Type *ETSChecker::CreateETSUnionType(ArenaVector &&constituent_types) ArenaVector new_constituent_types(Allocator()->Adapter()); for (auto *it : constituent_types) { - if (it->IsETSUnionType()) { - for (auto *type : it->AsETSUnionType()->ConstituentTypes()) { - new_constituent_types.push_back(type); - } - - continue; - } - new_constituent_types.push_back( - it->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE) ? PrimitiveTypeAsETSBuiltinType(it) : it); + it->HasTypeFlag(checker::TypeFlag::ETS_PRIMITIVE) ? BoxingConverter::ETSTypeFromSource(this, it) : it); } if (new_constituent_types.size() == 1) { @@ -151,7 +144,7 @@ Type *ETSChecker::CreateETSUnionType(ArenaVector &&constituent_types) auto *new_union_type = Allocator()->New(std::move(new_constituent_types)); new_union_type->SetLeastUpperBoundType(this); - return ETSUnionType::HandleUnionType(new_union_type); + return ETSUnionType::HandleUnionType(Relation(), new_union_type); } ETSFunctionType *ETSChecker::CreateETSFunctionType(ArenaVector &signatures) diff --git a/ets2panda/checker/types/ets/etsUnionType.cpp b/ets2panda/checker/types/ets/etsUnionType.cpp index b3335689a18c33baa4b690a8167e8452716399ba..190698ad4ea7e32b95e431024a21975159ef8072 100644 --- a/ets2panda/checker/types/ets/etsUnionType.cpp +++ b/ets2panda/checker/types/ets/etsUnionType.cpp @@ -106,13 +106,15 @@ void ETSUnionType::AssignmentTarget(TypeRelation *relation, Type *source) if (exact_type != constituent_types_.end()) { return; } + size_t assignable_count = 0; for (auto *it : constituent_types_) { if (relation->IsAssignableTo(ref_source, it)) { if (ref_source != source) { relation->IsAssignableTo(source, it); ASSERT(relation->IsTrue()); } - return; + ++assignable_count; + continue; } bool assign_primitive = it->IsETSObjectType() && it->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::UNBOXABLE_TYPE) && @@ -124,13 +126,88 @@ void ETSUnionType::AssignmentTarget(TypeRelation *relation, Type *source) source->Cast(relation, unboxed_it); ASSERT(relation->IsTrue()); } - return; + ++assignable_count; + } + } + if (assignable_count > 1) { + checker->ThrowTypeError({"Ambiguous assignment: after union normalization several types are assignable."}, + relation->GetNode()->Start()); + } + relation->Result(assignable_count != 0U); +} + +void ETSUnionType::LinearizeAndEraseIdentical(TypeRelation *relation, ArenaVector &constituent_types) +{ + auto *const checker = relation->GetChecker()->AsETSChecker(); + // Firstly, make linearization + ArenaVector copied_constituents(checker->Allocator()->Adapter()); + for (auto *ct : constituent_types) { + if (ct->IsETSUnionType()) { + auto other_types = ct->AsETSUnionType()->ConstituentTypes(); + copied_constituents.insert(copied_constituents.end(), other_types.begin(), other_types.end()); + } else { + copied_constituents.push_back(ct); + } + } + constituent_types = copied_constituents; + // Secondly, remove identical types + auto cmp_it = constituent_types.begin(); + while (cmp_it != constituent_types.end()) { + auto it = std::next(cmp_it); + while (it != constituent_types.end()) { + if (relation->IsIdenticalTo(*it, *cmp_it)) { + it = constituent_types.erase(it); + } else { + ++it; + } } + ++cmp_it; } } -Type *ETSUnionType::HandleUnionType(ETSUnionType *union_type) +void ETSUnionType::NormalizeTypes(TypeRelation *relation, ArenaVector &constituent_types) { + auto *const checker = relation->GetChecker()->AsETSChecker(); + auto ets_object = std::find(constituent_types.begin(), constituent_types.end(), + checker->GetGlobalTypesHolder()->GlobalETSObjectType()); + if (ets_object != constituent_types.end()) { + constituent_types.clear(); + constituent_types.push_back(checker->GetGlobalTypesHolder()->GlobalETSObjectType()); + return; + } + LinearizeAndEraseIdentical(relation, constituent_types); + // Find number type to remove other numeric types + auto number_found = + std::find_if(constituent_types.begin(), constituent_types.end(), [](Type *const ct) { + return ct->IsETSObjectType() && ct->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_DOUBLE); + }) != constituent_types.end(); + auto cmp_it = constituent_types.begin(); + while (cmp_it != constituent_types.end()) { + auto new_end = std::remove_if( + constituent_types.begin(), constituent_types.end(), [relation, checker, cmp_it, number_found](Type *ct) { + relation->Result(false); + (*cmp_it)->IsSupertypeOf(relation, ct); + bool remove_subtype = ct != *cmp_it && relation->IsTrue(); + bool remove_numeric = number_found && ct->IsETSObjectType() && + ct->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::UNBOXABLE_TYPE) && + !ct->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_DOUBLE) && + !ct->AsETSObjectType()->HasObjectFlag(ETSObjectFlags::BUILTIN_BOOLEAN); + bool remove_never = ct == checker->GetGlobalTypesHolder()->GlobalBuiltinNeverType(); + return remove_subtype || remove_numeric || remove_never; + }); + if (new_end != constituent_types.end()) { + constituent_types.erase(new_end, constituent_types.end()); + cmp_it = constituent_types.begin(); + continue; + } + ++cmp_it; + } +} + +Type *ETSUnionType::HandleUnionType([[maybe_unused]] TypeRelation *relation, ETSUnionType *union_type) +{ + NormalizeTypes(relation, union_type->constituent_types_); + if (union_type->ConstituentTypes().size() == 1) { return union_type->ConstituentTypes()[0]; } @@ -155,7 +232,7 @@ Type *ETSUnionType::Instantiate(ArenaAllocator *allocator, TypeRelation *relatio auto *new_union_type = allocator->New(std::move(copied_constituents)); new_union_type->SetLeastUpperBoundType(relation->GetChecker()->AsETSChecker()); - return HandleUnionType(new_union_type); + return HandleUnionType(relation, new_union_type); } void ETSUnionType::Cast(TypeRelation *relation, Type *target) diff --git a/ets2panda/checker/types/ets/etsUnionType.h b/ets2panda/checker/types/ets/etsUnionType.h index ce032fcef4712cb86bab8f42048c78a0057a698d..04573baca37950812492e1335dd13375db57804d 100644 --- a/ets2panda/checker/types/ets/etsUnionType.h +++ b/ets2panda/checker/types/ets/etsUnionType.h @@ -93,7 +93,9 @@ public: Type *FindExactOrBoxedType(ETSChecker *checker, Type *type) const; - static Type *HandleUnionType(ETSUnionType *union_type); + static void NormalizeTypes(TypeRelation *relation, ArenaVector &constituent_types); + + static Type *HandleUnionType(TypeRelation *relation, ETSUnionType *union_type); std::tuple ResolveConditionExpr() const override { @@ -109,6 +111,8 @@ private: static bool EachTypeRelatedToSomeType(TypeRelation *relation, ETSUnionType *source, ETSUnionType *target); static bool TypeRelatedToSomeType(TypeRelation *relation, Type *source, ETSUnionType *target); + static void LinearizeAndEraseIdentical(TypeRelation *relation, ArenaVector &constituent_types); + ArenaVector constituent_types_; Type *lub_type_ {nullptr}; }; diff --git a/ets2panda/parser/ETSparser.cpp b/ets2panda/parser/ETSparser.cpp index 744655bac5119de547b2c9384aeb7357fdc042d6..aa8f542de56c542446ce9cd1f37150c40254c428 100644 --- a/ets2panda/parser/ETSparser.cpp +++ b/ets2panda/parser/ETSparser.cpp @@ -2667,6 +2667,10 @@ std::pair ETSParser::GetTypeAnnotationFromToken(TypeAnnota type_annotation = ParseTypeAnnotation(options); type_annotation->SetStart(start_loc); + if (Lexer()->GetToken().Type() == lexer::TokenType::PUNCTUATOR_BITWISE_OR) { + type_annotation = ParseUnionType(type_annotation); + } + if (Lexer()->GetToken().Type() != lexer::TokenType::PUNCTUATOR_RIGHT_PARENTHESIS) { if (((*options) & TypeAnnotationParsingOptions::THROW_ERROR) != 0) { ThrowExpectedToken(lexer::TokenType::PUNCTUATOR_RIGHT_PARENTHESIS); diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index 7717c0c09e59ab145560d671af3316dabd9c9a21..fece57326cc1abaa1595d195f82b5490aec49ede 100644 --- a/ets2panda/test/CMakeLists.txt +++ b/ets2panda/test/CMakeLists.txt @@ -147,6 +147,19 @@ if(PANDA_WITH_ETS) ${PANDA_SANITIZERS_LIST} ) + panda_add_gtest( + NAME es2panda_union_normalization_tests + SOURCES + unit/union_normalization_test.cpp + LIBRARIES + es2panda-public es2panda-lib arkassembler arkbytecodeopt + INCLUDE_DIRS + ${ES2PANDA_PATH} + ${ES2PANDA_BINARY_ROOT} + SANITIZERS + ${PANDA_SANITIZERS_LIST} + ) + add_subdirectory(tsconfig) endif() diff --git a/ets2panda/test/unit/union_normalization_test.cpp b/ets2panda/test/unit/union_normalization_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6da53b1a0bbed13429a1f015ea7c33197ee3a60a --- /dev/null +++ b/ets2panda/test/unit/union_normalization_test.cpp @@ -0,0 +1,457 @@ +/** + * Copyright (c) 2021-2023 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 +#include + +#include "checker/ETSAnalyzer.h" +#include "checker/ETSchecker.h" +#include "compiler/core/compilerImpl.h" +#include "compiler/core/ETSCompiler.h" +#include "compiler/core/ETSemitter.h" +#include "compiler/core/ETSGen.h" +#include "compiler/core/regSpiller.h" +#include "compiler/lowering/phase.h" +#include "es2panda.h" +#include "mem/arena_allocator.h" +#include "mem/pool_manager.h" +#include "public/public.h" +#include "util/arktsconfig.h" +#include "util/generateBin.h" +#include "varbinder/ETSBinder.h" + +namespace panda::es2panda { + +class UnionNormalizationTest : public testing::Test { +public: + UnionNormalizationTest() + { + allocator_ = std::make_unique(SpaceType::SPACE_TYPE_COMPILER); + public_context_ = std::make_unique(); + } + + ~UnionNormalizationTest() override = default; + + static void SetUpTestCase() + { + constexpr auto COMPILER_SIZE = operator""_MB(256ULL); + mem::MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); + PoolManager::Initialize(); + } + + ArenaAllocator *Allocator() + { + return allocator_.get(); + } + + void InitializeChecker(const char **argv, std::string_view file_name, std::string_view src, + checker::ETSChecker *checker, parser::Program *program) + { + InitializeChecker(argv, file_name, src, checker, program); + } + + template + compiler::CompilerContext::CodeGenCb MakeCompileJob() + { + return [this](compiler::CompilerContext *context, varbinder::FunctionScope *scope, + compiler::ProgramElement *program_element) -> void { + RegSpiller reg_spiller; + AstCompiler astcompiler; + CodeGen cg(allocator_.get(), ®_spiller, context, scope, program_element, &astcompiler); + FunctionEmitter func_emitter(&cg, program_element); + func_emitter.Generate(); + }; + } + + template + void InitializeChecker(const char **argv, std::string_view file_name, std::string_view src, + checker::ETSChecker *checker, parser::Program *program) + { + auto options = std::make_unique(); + if (!options->Parse(1, argv)) { + std::cerr << options->ErrorMsg() << std::endl; + return; + } + + panda::Logger::ComponentMask mask {}; + mask.set(panda::Logger::Component::ES2PANDA); + panda::Logger::InitializeStdLogging(panda::Logger::LevelFromString(options->LogLevel()), mask); + + Compiler compiler(options->Extension(), options->ThreadCount()); + SourceFile input(file_name, src, options->ParseModule()); + compiler::CompilationUnit unit {input, options->CompilerOptions(), 0, options->Extension()}; + auto get_phases = compiler::GetPhaseList(ScriptExtension::ETS); + + program->MarkEntry(); + auto parser = Parser(program, unit.options, static_cast(unit.raw_parser_status)); + auto analyzer = Analyzer(checker); + checker->SetAnalyzer(&analyzer); + + auto *varbinder = program->VarBinder(); + varbinder->SetProgram(program); + + compiler::CompilerContext context(varbinder, checker, unit.options, + MakeCompileJob()); + varbinder->SetCompilerContext(&context); + + auto emitter = Emitter(&context); + context.SetEmitter(&emitter); + context.SetParser(&parser); + + public_context_->source_file = &unit.input; + public_context_->allocator = allocator_.get(); + public_context_->parser = &parser; + public_context_->checker = context.Checker(); + public_context_->analyzer = public_context_->checker->GetAnalyzer(); + public_context_->compiler_context = &context; + public_context_->emitter = context.GetEmitter(); + + parser.ParseScript(unit.input, unit.options.compilation_mode == CompilationMode::GEN_STD_LIB); + if constexpr (std::is_same_v && std::is_same_v) { + reinterpret_cast(varbinder)->FillResolvedImportPathes( + parser.ResolvedParsedSourcesMap(), allocator_.get()); + } + for (auto *phase : get_phases) { + if (!phase->Apply(public_context_.get(), program)) { + return; + } + } + } + + static checker::Type *FindClassType(varbinder::ETSBinder *varbinder, std::string_view class_name) + { + auto class_defs = varbinder->AsETSBinder()->GetRecordTable()->ClassDefinitions(); + auto base_class = std::find_if(class_defs.begin(), class_defs.end(), [class_name](ir::ClassDefinition *cdef) { + return cdef->Ident()->Name().Is(class_name); + }); + if (base_class == class_defs.end()) { + return nullptr; + } + return (*base_class)->TsType(); + } + + static checker::Type *FindTypeAlias(checker::ETSChecker *checker, std::string_view alias_name) + { + auto *found_var = + checker->Scope()->FindLocal(alias_name, varbinder::ResolveBindingOptions::ALL)->AsLocalVariable(); + if (found_var == nullptr) { + return nullptr; + } + return found_var->Declaration()->Node()->AsTSTypeAliasDeclaration()->TypeAnnotation()->TsType(); + } + + NO_COPY_SEMANTIC(UnionNormalizationTest); + NO_MOVE_SEMANTIC(UnionNormalizationTest); + +protected: + static constexpr uint8_t SIZE2 = 2; + static constexpr uint8_t SIZE3 = 3; + static constexpr uint8_t IDX0 = 0; + static constexpr uint8_t IDX1 = 1; + static constexpr uint8_t IDX2 = 2; + +private: + std::unique_ptr allocator_; + std::unique_ptr public_context_; +}; + +TEST_F(UnionNormalizationTest, UnionWithObject) +{ + // Test normalization: int | Object | string ==> Object + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "", &checker, &program); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(checker.GlobalIntType()); + union_constituents.emplace_back(checker.GetGlobalTypesHolder()->GlobalETSObjectType()); + union_constituents.emplace_back(checker.GetGlobalTypesHolder()->GlobalETSStringBuiltinType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSObjectType()); + ASSERT_EQ(normalized_type, checker.GlobalETSObjectType()); +} + +TEST_F(UnionNormalizationTest, UnionWithIdenticalTypes1) +{ + // Test normalization: number | Base | string | number ==> number | Base | string + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "class Base {}", &checker, &program); + + auto *const base_type = FindClassType(program.VarBinder()->AsETSBinder(), "Base"); + ASSERT_NE(base_type, nullptr); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(checker.GlobalDoubleType()); + union_constituents.emplace_back(base_type); + union_constituents.emplace_back(checker.GlobalBuiltinETSStringType()); + union_constituents.emplace_back(checker.GlobalDoubleType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSUnionType()); + auto *const union_type = normalized_type->AsETSUnionType(); + ASSERT_EQ(union_type->ConstituentTypes().size(), SIZE3); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX0), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX1), base_type); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX2), checker.GlobalBuiltinETSStringType()); +} + +TEST_F(UnionNormalizationTest, UnionWithIdenticalTypes2) +{ + // Test normalization: Base | int | Base | double | short | number ==> Base | number + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "class Base {}", &checker, &program); + + auto *const base_type = FindClassType(program.VarBinder()->AsETSBinder(), "Base"); + ASSERT_NE(base_type, nullptr); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(base_type); + union_constituents.emplace_back(checker.GlobalIntType()); + union_constituents.emplace_back(base_type); + union_constituents.emplace_back(checker.GlobalDoubleType()); + union_constituents.emplace_back(checker.GlobalShortType()); + union_constituents.emplace_back(checker.GlobalDoubleType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSUnionType()); + auto *const union_type = normalized_type->AsETSUnionType(); + ASSERT_EQ(union_type->ConstituentTypes().size(), SIZE2); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX0), base_type); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX1), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); +} + +TEST_F(UnionNormalizationTest, UnionWithNumeric1) +{ + // Test normalization: boolean | int | double | short ==> boolean | double + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "", &checker, &program); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(checker.GlobalETSBooleanType()); + union_constituents.emplace_back(checker.GlobalIntType()); + union_constituents.emplace_back(checker.GlobalDoubleType()); + union_constituents.emplace_back(checker.GlobalShortType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSUnionType()); + auto *const union_type = normalized_type->AsETSUnionType(); + ASSERT_EQ(union_type->ConstituentTypes().size(), SIZE2); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX0), checker.GetGlobalTypesHolder()->GlobalETSBooleanBuiltinType()); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX1), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); +} + +TEST_F(UnionNormalizationTest, UnionWithNumeric2) +{ + // Test normalization: string | int | Base | double | short ==> string | Base | double + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "class Base {}", &checker, &program); + + auto *const base_type = FindClassType(program.VarBinder()->AsETSBinder(), "Base"); + ASSERT_NE(base_type, nullptr); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(checker.GlobalBuiltinETSStringType()); + union_constituents.emplace_back(checker.GlobalIntType()); + union_constituents.emplace_back(base_type); + union_constituents.emplace_back(checker.GlobalDoubleType()); + union_constituents.emplace_back(checker.GlobalShortType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSUnionType()); + auto *const union_type = normalized_type->AsETSUnionType(); + ASSERT_EQ(union_type->ConstituentTypes().size(), SIZE3); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX0), checker.GlobalBuiltinETSStringType()); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX1), base_type); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX2), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); +} + +TEST_F(UnionNormalizationTest, UnionWithSubTypes) +{ + // Test 4 cases of normalization + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + static constexpr std::string_view SRC = + "\ + class Base {}\ + class Derived1 extends Base {}\ + class Derived2 extends Base {}\ + "; + InitializeChecker(&argv, "_.ets", SRC, &checker, &program); + + auto *const base_type = FindClassType(program.VarBinder()->AsETSBinder(), "Base"); + ASSERT_NE(base_type, nullptr); + auto *const derived1_type = FindClassType(program.VarBinder()->AsETSBinder(), "Derived1"); + ASSERT_NE(derived1_type, nullptr); + auto *const derived2_type = FindClassType(program.VarBinder()->AsETSBinder(), "Derived2"); + ASSERT_NE(derived2_type, nullptr); + + // Test normalization: Derived1 | Base ==> Base + ArenaVector union_constituents1(checker.Allocator()->Adapter()); + union_constituents1.emplace_back(derived1_type); + union_constituents1.emplace_back(base_type); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type1 = checker.CreateETSUnionType(std::move(union_constituents1)); + ASSERT_NE(normalized_type1, nullptr); + ASSERT_TRUE(normalized_type1->IsETSObjectType()); + ASSERT_EQ(normalized_type1, base_type); + + // Test normalization: Base | Derived2 ==> Base + ArenaVector union_constituents2(checker.Allocator()->Adapter()); + union_constituents2.emplace_back(base_type); + union_constituents2.emplace_back(derived2_type); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type2 = checker.CreateETSUnionType(std::move(union_constituents2)); + ASSERT_NE(normalized_type2, nullptr); + ASSERT_TRUE(normalized_type2->IsETSObjectType()); + ASSERT_EQ(normalized_type2, base_type); + + // Test normalization: Derived1 | Derived2 ==> Derived1 | Derived2 + ArenaVector union_constituents3(checker.Allocator()->Adapter()); + union_constituents3.emplace_back(derived1_type); + union_constituents3.emplace_back(derived2_type); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type3 = checker.CreateETSUnionType(std::move(union_constituents3)); + ASSERT_NE(normalized_type3, nullptr); + auto *const union_type = normalized_type3->AsETSUnionType(); + ASSERT_EQ(union_type->ConstituentTypes().size(), SIZE2); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX0), derived1_type); + ASSERT_EQ(union_type->ConstituentTypes().at(IDX1), derived2_type); + + // Test normalization: Derived2 | Base | Derived1 ==> Base + ArenaVector union_constituents4(checker.Allocator()->Adapter()); + union_constituents4.emplace_back(derived1_type); + union_constituents4.emplace_back(base_type); + union_constituents4.emplace_back(derived2_type); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type4 = checker.CreateETSUnionType(std::move(union_constituents4)); + ASSERT_NE(normalized_type4, nullptr); + ASSERT_TRUE(normalized_type4->IsETSObjectType()); + ASSERT_EQ(normalized_type4, base_type); +} + +TEST_F(UnionNormalizationTest, UnionLinearization) +{ + // Test 3 cases of normalization + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + static constexpr std::string_view SRC = + "\ + class Base {}\ + class Derived1 extends Base {}\ + class Derived2 extends Base {}\ + type UT = int | string\ + \ + type UT1 = int | (int | string) | number\ + type UT2 = int | UT | number\ + type UT3 = int | (Derived2 | Base) | Derived1 | (string | number | short) | (int | string)\ + "; + InitializeChecker(&argv, "_.ets", SRC, &checker, &program); + + auto *varbinder = program.VarBinder()->AsETSBinder(); + auto *const base_type = FindClassType(varbinder, "Base"); + ASSERT_NE(base_type, nullptr); + auto *const derived1_type = FindClassType(program.VarBinder()->AsETSBinder(), "Derived1"); + ASSERT_NE(derived1_type, nullptr); + auto *const derived2_type = FindClassType(program.VarBinder()->AsETSBinder(), "Derived2"); + ASSERT_NE(derived2_type, nullptr); + + // Test normalization: int | (int | string) | number ==> string | number + auto *const ut1_type = FindTypeAlias(&checker, "UT1"); + ASSERT_NE(ut1_type, nullptr); + ASSERT_TRUE(ut1_type->IsETSUnionType()); + auto *ut1 = ut1_type->AsETSUnionType(); + ASSERT_EQ(ut1->ConstituentTypes().size(), SIZE2); + ASSERT_EQ(ut1->ConstituentTypes().at(IDX0), checker.GlobalBuiltinETSStringType()); + ASSERT_EQ(ut1->ConstituentTypes().at(IDX1), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); + + // Test normalization: int | UT | number ==> string | number + auto *const ut2_type = FindTypeAlias(&checker, "UT2"); + ASSERT_NE(ut2_type, nullptr); + ASSERT_TRUE(ut2_type->IsETSUnionType()); + auto *ut2 = ut2_type->AsETSUnionType(); + ASSERT_EQ(ut2->ConstituentTypes().size(), SIZE2); + ASSERT_EQ(ut2->ConstituentTypes().at(IDX0), checker.GlobalBuiltinETSStringType()); + ASSERT_EQ(ut2->ConstituentTypes().at(IDX1), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); + + // Test normalization: + // int | (Derived2 | Base) | Derived1 | (string | number | short) | (int | string) ==> Base | string | number + auto *const ut3_type = FindTypeAlias(&checker, "UT3"); + ASSERT_NE(ut3_type, nullptr); + ASSERT_TRUE(ut3_type->IsETSUnionType()); + auto *ut3 = ut3_type->AsETSUnionType(); + ASSERT_EQ(ut3->ConstituentTypes().size(), SIZE3); + ASSERT_EQ(ut3->ConstituentTypes().at(IDX0), base_type); + ASSERT_EQ(ut3->ConstituentTypes().at(IDX1), checker.GlobalBuiltinETSStringType()); + ASSERT_EQ(ut3->ConstituentTypes().at(IDX2), checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); +} + +TEST_F(UnionNormalizationTest, UnionWithNever) +{ + // Test normalization: int | never | number ==> number + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + const char *argv = "../../../bin/es2panda"; + checker::ETSChecker checker; + auto program = parser::Program::NewProgram(Allocator()); + InitializeChecker(&argv, "_.ets", "", &checker, &program); + + ArenaVector union_constituents(checker.Allocator()->Adapter()); + union_constituents.emplace_back(checker.GlobalIntType()); + union_constituents.emplace_back(checker.GetGlobalTypesHolder()->GlobalBuiltinNeverType()); + union_constituents.emplace_back(checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); + + // Create union type, which will be normalized inside creation function + auto *const normalized_type = checker.CreateETSUnionType(std::move(union_constituents)); + ASSERT_NE(normalized_type, nullptr); + ASSERT_TRUE(normalized_type->IsETSObjectType()); + ASSERT_EQ(normalized_type, checker.GetGlobalTypesHolder()->GlobalDoubleBuiltinType()); +} + +} // namespace panda::es2panda