// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Parser.h" #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" #include "Fixture.h" #include "ScopedFlags.h" #include "doctest.h" using namespace Luau; TEST_SUITE_BEGIN("TypeVarTests"); TEST_CASE_FIXTURE(Fixture, "primitives_are_equal") { REQUIRE_EQ(typeChecker.booleanType, typeChecker.booleanType); } TEST_CASE_FIXTURE(Fixture, "bound_type_is_equal_to_that_which_it_is_bound") { TypeVar bound(BoundTypeVar(typeChecker.booleanType)); REQUIRE_EQ(bound, *typeChecker.booleanType); } TEST_CASE_FIXTURE(Fixture, "equivalent_cyclic_tables_are_equal") { TypeVar cycleOne{TypeVariant(TableTypeVar())}; TableTypeVar* tableOne = getMutable(&cycleOne); tableOne->props["self"] = {&cycleOne}; TypeVar cycleTwo{TypeVariant(TableTypeVar())}; TableTypeVar* tableTwo = getMutable(&cycleTwo); tableTwo->props["self"] = {&cycleTwo}; CHECK_EQ(cycleOne, cycleTwo); } TEST_CASE_FIXTURE(Fixture, "different_cyclic_tables_are_not_equal") { TypeVar cycleOne{TypeVariant(TableTypeVar())}; TableTypeVar* tableOne = getMutable(&cycleOne); tableOne->props["self"] = {&cycleOne}; TypeVar cycleTwo{TypeVariant(TableTypeVar())}; TableTypeVar* tableTwo = getMutable(&cycleTwo); tableTwo->props["this"] = {&cycleTwo}; CHECK_NE(cycleOne, cycleTwo); } TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_not_parenthesized_if_just_one_value") { auto emptyArgumentPack = TypePackVar{TypePack{}}; auto returnPack = TypePackVar{TypePack{{typeChecker.numberType}}}; auto returnsTwo = TypeVar(FunctionTypeVar(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack)); std::string res = toString(&returnsTwo); CHECK_EQ("() -> number", res); } TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_not_just_one_value") { auto emptyArgumentPack = TypePackVar{TypePack{}}; auto returnPack = TypePackVar{TypePack{{typeChecker.numberType, typeChecker.numberType}}}; auto returnsTwo = TypeVar(FunctionTypeVar(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack)); std::string res = toString(&returnsTwo); CHECK_EQ("() -> (number, number)", res); } TEST_CASE_FIXTURE(Fixture, "return_type_of_function_is_parenthesized_if_tail_is_free") { auto emptyArgumentPack = TypePackVar{TypePack{}}; auto free = Unifiable::Free(TypeLevel()); auto freePack = TypePackVar{TypePackVariant{free}}; auto returnPack = TypePackVar{TypePack{{typeChecker.numberType}, &freePack}}; auto returnsTwo = TypeVar(FunctionTypeVar(typeChecker.globalScope->level, &emptyArgumentPack, &returnPack)); std::string res = toString(&returnsTwo); CHECK_EQ(res, "() -> (number, a...)"); } TEST_CASE_FIXTURE(Fixture, "subset_check") { UnionTypeVar super, sub, notSub; super.options = {typeChecker.numberType, typeChecker.stringType, typeChecker.booleanType}; sub.options = {typeChecker.numberType, typeChecker.stringType}; notSub.options = {typeChecker.numberType, typeChecker.nilType}; CHECK(isSubset(super, sub)); CHECK(!isSubset(super, notSub)); } TEST_CASE_FIXTURE(Fixture, "iterate_over_UnionTypeVar") { UnionTypeVar utv; utv.options = {typeChecker.numberType, typeChecker.stringType, typeChecker.anyType}; std::vector result; for (TypeId ty : &utv) result.push_back(ty); CHECK(result == utv.options); } TEST_CASE_FIXTURE(Fixture, "iterating_over_nested_UnionTypeVars") { TypeVar subunion{UnionTypeVar{}}; UnionTypeVar* innerUtv = getMutable(&subunion); innerUtv->options = {typeChecker.numberType, typeChecker.stringType}; UnionTypeVar utv; utv.options = {typeChecker.anyType, &subunion}; std::vector result; for (TypeId ty : &utv) result.push_back(ty); REQUIRE_EQ(result.size(), 3); CHECK_EQ(result[0], typeChecker.anyType); CHECK_EQ(result[2], typeChecker.stringType); CHECK_EQ(result[1], typeChecker.numberType); } TEST_CASE_FIXTURE(Fixture, "iterator_detects_cyclic_UnionTypeVars_and_skips_over_them") { TypeVar atv{UnionTypeVar{}}; UnionTypeVar* utv1 = getMutable(&atv); TypeVar btv{UnionTypeVar{}}; UnionTypeVar* utv2 = getMutable(&btv); utv2->options.push_back(typeChecker.numberType); utv2->options.push_back(typeChecker.stringType); utv2->options.push_back(&atv); utv1->options.push_back(&btv); std::vector result; for (TypeId ty : utv2) result.push_back(ty); REQUIRE_EQ(result.size(), 2); CHECK_EQ(result[0], typeChecker.numberType); CHECK_EQ(result[1], typeChecker.stringType); } TEST_CASE_FIXTURE(Fixture, "iterator_descends_on_nested_in_first_operator*") { TypeVar tv1{UnionTypeVar{{typeChecker.stringType, typeChecker.numberType}}}; TypeVar tv2{UnionTypeVar{{&tv1, typeChecker.booleanType}}}; auto utv = get(&tv2); std::vector result; for (TypeId ty : utv) result.push_back(ty); REQUIRE_EQ(result.size(), 3); CHECK_EQ(result[0], typeChecker.stringType); CHECK_EQ(result[1], typeChecker.numberType); CHECK_EQ(result[2], typeChecker.booleanType); } TEST_CASE_FIXTURE(Fixture, "UnionTypeVarIterator_with_vector_iter_ctor") { TypeVar tv1{UnionTypeVar{{typeChecker.stringType, typeChecker.numberType}}}; TypeVar tv2{UnionTypeVar{{&tv1, typeChecker.booleanType}}}; auto utv = get(&tv2); std::vector actual(begin(utv), end(utv)); std::vector expected{typeChecker.stringType, typeChecker.numberType, typeChecker.booleanType}; CHECK_EQ(actual, expected); } TEST_CASE_FIXTURE(Fixture, "UnionTypeVarIterator_with_empty_union") { TypeVar tv{UnionTypeVar{}}; auto utv = get(&tv); std::vector actual(begin(utv), end(utv)); CHECK(actual.empty()); } TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure") { TypeVar ftv11{FreeTypeVar{TypeLevel{}}}; TypePackVar tp24{TypePack{{&ftv11}}}; TypePackVar tp17{TypePack{}}; TypeVar ftv23{FunctionTypeVar{&tp24, &tp17}}; TypeVar ttvConnection2{TableTypeVar{}}; TableTypeVar* ttvConnection2_ = getMutable(&ttvConnection2); ttvConnection2_->instantiatedTypeParams.push_back(&ftv11); ttvConnection2_->props["f"] = {&ftv23}; TypePackVar tp21{TypePack{{&ftv11}}}; TypePackVar tp20{TypePack{}}; TypeVar ftv19{FunctionTypeVar{&tp21, &tp20}}; TypeVar ttvSignal{TableTypeVar{}}; TableTypeVar* ttvSignal_ = getMutable(&ttvSignal); ttvSignal_->instantiatedTypeParams.push_back(&ftv11); ttvSignal_->props["f"] = {&ftv19}; // Back edge ttvConnection2_->props["signal"] = {&ttvSignal}; TypeVar gtvK2{GenericTypeVar{}}; TypeVar gtvV2{GenericTypeVar{}}; TypeVar ttvTweenResult2{TableTypeVar{}}; TableTypeVar* ttvTweenResult2_ = getMutable(&ttvTweenResult2); ttvTweenResult2_->instantiatedTypeParams.push_back(>vK2); ttvTweenResult2_->instantiatedTypeParams.push_back(>vV2); TypePackVar tp13{TypePack{{&ttvTweenResult2}}}; TypeVar ftv12{FunctionTypeVar{&tp13, &tp17}}; TypeVar ttvConnection{TableTypeVar{}}; TableTypeVar* ttvConnection_ = getMutable(&ttvConnection); ttvConnection_->instantiatedTypeParams.push_back(&ttvTweenResult2); ttvConnection_->props["f"] = {&ftv12}; ttvConnection_->props["signal"] = {&ttvSignal}; TypePackVar tp9{TypePack{}}; TypePackVar tp10{TypePack{{&ttvConnection}}}; TypeVar ftv8{FunctionTypeVar{&tp9, &tp10}}; TypeVar ttvTween{TableTypeVar{}}; TableTypeVar* ttvTween_ = getMutable(&ttvTween); ttvTween_->instantiatedTypeParams.push_back(>vK2); ttvTween_->instantiatedTypeParams.push_back(>vV2); ttvTween_->props["f"] = {&ftv8}; TypePackVar tp4{TypePack{}}; TypePackVar tp5{TypePack{{&ttvTween}}}; TypeVar ftv3{FunctionTypeVar{&tp4, &tp5}}; // Back edge ttvTweenResult2_->props["f"] = {&ftv3}; TypeVar gtvK{GenericTypeVar{}}; TypeVar gtvV{GenericTypeVar{}}; TypeVar ttvTweenResult{TableTypeVar{}}; TableTypeVar* ttvTweenResult_ = getMutable(&ttvTweenResult); ttvTweenResult_->instantiatedTypeParams.push_back(>vK); ttvTweenResult_->instantiatedTypeParams.push_back(>vV); ttvTweenResult_->props["f"] = {&ftv3}; TypeId root = &ttvTweenResult; typeChecker.currentModule = std::make_shared(); TypeId result = typeChecker.anyify(typeChecker.globalScope, root, Location{}); CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(result)); } TEST_CASE("tagging_tables") { ScopedFastFlag sff{"LuauRefactorTagging", true}; TypeVar ttv{TableTypeVar{}}; CHECK(!Luau::hasTag(&ttv, "foo")); Luau::attachTag(&ttv, "foo"); CHECK(Luau::hasTag(&ttv, "foo")); } TEST_CASE("tagging_classes") { ScopedFastFlag sff{"LuauRefactorTagging", true}; TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr}}; CHECK(!Luau::hasTag(&base, "foo")); Luau::attachTag(&base, "foo"); CHECK(Luau::hasTag(&base, "foo")); } TEST_CASE("tagging_subclasses") { ScopedFastFlag sff{"LuauRefactorTagging", true}; TypeVar base{ClassTypeVar{"Base", {}, std::nullopt, std::nullopt, {}, nullptr}}; TypeVar derived{ClassTypeVar{"Derived", {}, &base, std::nullopt, {}, nullptr}}; CHECK(!Luau::hasTag(&base, "foo")); CHECK(!Luau::hasTag(&derived, "foo")); Luau::attachTag(&base, "foo"); CHECK(Luau::hasTag(&base, "foo")); CHECK(Luau::hasTag(&derived, "foo")); Luau::attachTag(&derived, "bar"); CHECK(!Luau::hasTag(&base, "bar")); CHECK(Luau::hasTag(&derived, "bar")); } TEST_CASE("tagging_functions") { ScopedFastFlag sff{"LuauRefactorTagging", true}; TypePackVar empty{TypePack{}}; TypeVar ftv{FunctionTypeVar{&empty, &empty}}; CHECK(!Luau::hasTag(&ftv, "foo")); Luau::attachTag(&ftv, "foo"); CHECK(Luau::hasTag(&ftv, "foo")); } TEST_CASE("tagging_props") { ScopedFastFlag sff{"LuauRefactorTagging", true}; Property prop{}; CHECK(!Luau::hasTag(prop, "foo")); Luau::attachTag(prop, "foo"); CHECK(Luau::hasTag(prop, "foo")); } TEST_SUITE_END();