// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BuiltinDefinitions.h" #include "Luau/Parser.h" #include "Luau/TypeInfer.h" #include "Luau/TypeVar.h" #include "Fixture.h" #include "doctest.h" using namespace Luau; LUAU_FASTFLAG(LuauUseCommittingTxnLog) TEST_SUITE_BEGIN("TypePackTests"); TEST_CASE_FIXTURE(Fixture, "infer_multi_return") { CheckResult result = check(R"( function take_two() return 2, 2 end )"); LUAU_REQUIRE_NO_ERRORS(result); const FunctionTypeVar* takeTwoType = get<FunctionTypeVar>(requireType("take_two")); REQUIRE(takeTwoType != nullptr); const auto& [returns, tail] = flatten(takeTwoType->retType); CHECK_EQ(2, returns.size()); CHECK_EQ(typeChecker.numberType, returns[0]); CHECK_EQ(typeChecker.numberType, returns[1]); CHECK(!tail); } TEST_CASE_FIXTURE(Fixture, "empty_varargs_should_return_nil_when_not_in_tail_position") { CheckResult result = check(R"( local a, b = ..., 1 )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "self_and_varargs_should_work") { CheckResult result = check(R"( local t = {} function t:f(...) end t:f(1) )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "last_element_of_return_statement_can_itself_be_a_pack") { CheckResult result = check(R"( function take_two() return 2, 2 end function take_three() return 1, take_two() end )"); LUAU_REQUIRE_NO_ERRORS(result); dumpErrors(result); const FunctionTypeVar* takeOneMoreType = get<FunctionTypeVar>(requireType("take_three")); REQUIRE(takeOneMoreType != nullptr); const auto& [rets, tail] = flatten(takeOneMoreType->retType); REQUIRE_EQ(3, rets.size()); CHECK_EQ(typeChecker.numberType, rets[0]); CHECK_EQ(typeChecker.numberType, rets[1]); CHECK_EQ(typeChecker.numberType, rets[2]); CHECK(!tail); } TEST_CASE_FIXTURE(Fixture, "higher_order_function") { CheckResult result = check(R"( function apply(f, g, x) return f(g(x)) end )"); LUAU_REQUIRE_NO_ERRORS(result); const FunctionTypeVar* applyType = get<FunctionTypeVar>(requireType("apply")); REQUIRE(applyType != nullptr); std::vector<TypeId> applyArgs = flatten(applyType->argTypes).first; REQUIRE_EQ(3, applyArgs.size()); const FunctionTypeVar* fType = get<FunctionTypeVar>(follow(applyArgs[0])); REQUIRE(fType != nullptr); const FunctionTypeVar* gType = get<FunctionTypeVar>(follow(applyArgs[1])); REQUIRE(gType != nullptr); std::vector<TypeId> gArgs = flatten(gType->argTypes).first; REQUIRE_EQ(1, gArgs.size()); // function(function(t1, T2...): (t3, T4...), function(t5): (t1, T2...), t5): (t3, T4...) REQUIRE_EQ(*gArgs[0], *applyArgs[2]); REQUIRE_EQ(toString(fType->argTypes), toString(gType->retType)); REQUIRE_EQ(toString(fType->retType), toString(applyType->retType)); } TEST_CASE_FIXTURE(Fixture, "return_type_should_be_empty_if_nothing_is_returned") { CheckResult result = check(R"( function f() end function g() return end )"); LUAU_REQUIRE_NO_ERRORS(result); const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f")); REQUIRE(fTy != nullptr); CHECK_EQ(0, size(fTy->retType)); const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g")); REQUIRE(gTy != nullptr); CHECK_EQ(0, size(gTy->retType)); } TEST_CASE_FIXTURE(Fixture, "no_return_size_should_be_zero") { CheckResult result = check(R"( function f(a:any) return a end function g() return end function h() end g(h()) f(g(),h()) )"); LUAU_REQUIRE_NO_ERRORS(result); const FunctionTypeVar* fTy = get<FunctionTypeVar>(requireType("f")); REQUIRE(fTy != nullptr); CHECK_EQ(1, size(follow(fTy->retType))); const FunctionTypeVar* gTy = get<FunctionTypeVar>(requireType("g")); REQUIRE(gTy != nullptr); CHECK_EQ(0, size(gTy->retType)); const FunctionTypeVar* hTy = get<FunctionTypeVar>(requireType("h")); REQUIRE(hTy != nullptr); CHECK_EQ(0, size(hTy->retType)); } TEST_CASE_FIXTURE(Fixture, "varargs_inference_through_multiple_scopes") { CheckResult result = check(R"( local function f(...) do local a: string = ... local b: number = ... end end f("foo") )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "multiple_varargs_inference_are_not_confused") { CheckResult result = check(R"( local function f(...) local a: string = ... return function(...) local b: number = ... end end f("foo", "bar")(1, 2) )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "parenthesized_varargs_returns_any") { CheckResult result = check(R"( --!strict local value local function f(...) value = ... end )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("any", toString(requireType("value"))); } TEST_CASE_FIXTURE(Fixture, "variadic_packs") { TypeArena& arena = typeChecker.globalTypes; unfreeze(arena); TypePackId listOfNumbers = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.numberType}}); TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{typeChecker.stringType}}); // clang-format off addGlobalBinding(typeChecker, "foo", arena.addType( FunctionTypeVar{ listOfNumbers, arena.addTypePack({typeChecker.numberType}) } ), "@test" ); addGlobalBinding(typeChecker, "bar", arena.addType( FunctionTypeVar{ arena.addTypePack({{typeChecker.numberType}, listOfStrings}), arena.addTypePack({typeChecker.numberType}) } ), "@test" ); // clang-format on freeze(arena); CheckResult result = check(R"( --!strict foo(1, 2, 3, "foo") bar(1, "foo", "bar", 3) )"); LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK_EQ(result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{typeChecker.numberType, typeChecker.stringType}})); CHECK_EQ(result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{typeChecker.stringType, typeChecker.numberType}})); } TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax") { CheckResult result = check(R"( --!strict local function foo(...: number) end foo(1, 2, 3, 4, 5, 6) )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("foo")), "(...number) -> ()"); } // Switch back to TEST_CASE_FIXTURE with regular Fixture when removing the // LuauUseCommittingTxnLog flag. TEST_CASE("type_pack_hidden_free_tail_infinite_growth") { Fixture fix(FFlag::LuauUseCommittingTxnLog); CheckResult result = fix.check(R"( --!nonstrict if _ then _[function(l0)end],l0 = _ elseif _ then return l0(nil) elseif 1 / l0(nil) then elseif _ then return #_,l0() end )"); // Switch back to LUAU_REQUIRE_ERRORS(result) when using TEST_CASE_FIXTURE. CHECK(result.errors.size() > 0); } TEST_CASE_FIXTURE(Fixture, "variadic_argument_tail") { CheckResult result = check(R"( local _ = function():((...any)->(...any),()->()) return function() end, function() end end for y in _() do end )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs") { CheckResult result = check(R"( type Packed<T...> = (T...) -> T... local a: Packed<> local b: Packed<number> local c: Packed<string, number> )"); LUAU_REQUIRE_NO_ERRORS(result); auto tf = lookupType("Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "(T...) -> (T...)"); CHECK_EQ(toString(requireType("a")), "() -> ()"); CHECK_EQ(toString(requireType("b")), "(number) -> number"); CHECK_EQ(toString(requireType("c")), "(string, number) -> (string, number)"); result = check(R"( -- (U..., T) cannot be parsed right now type Packed<T, U...> = { f: (a: T, U...) -> (T, U...) } local a: Packed<number> local b: Packed<string, number> local c: Packed<string, number, boolean> )"); LUAU_REQUIRE_NO_ERRORS(result); tf = lookupType("Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed<T, U...>"); CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}"); auto ttvA = get<TableTypeVar>(requireType("a")); REQUIRE(ttvA); CHECK_EQ(toString(requireType("a")), "Packed<number>"); CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> (number) |}"); REQUIRE(ttvA->instantiatedTypeParams.size() == 1); REQUIRE(ttvA->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number"); CHECK_EQ(toString(ttvA->instantiatedTypePackParams[0], {true}), ""); auto ttvB = get<TableTypeVar>(requireType("b")); REQUIRE(ttvB); CHECK_EQ(toString(requireType("b")), "Packed<string, number>"); CHECK_EQ(toString(requireType("b"), {true}), "{| f: (string, number) -> (string, number) |}"); REQUIRE(ttvB->instantiatedTypeParams.size() == 1); REQUIRE(ttvB->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvB->instantiatedTypeParams[0], {true}), "string"); CHECK_EQ(toString(ttvB->instantiatedTypePackParams[0], {true}), "number"); auto ttvC = get<TableTypeVar>(requireType("c")); REQUIRE(ttvC); CHECK_EQ(toString(requireType("c")), "Packed<string, number, boolean>"); CHECK_EQ(toString(requireType("c"), {true}), "{| f: (string, number, boolean) -> (string, number, boolean) |}"); REQUIRE(ttvC->instantiatedTypeParams.size() == 1); REQUIRE(ttvC->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvC->instantiatedTypeParams[0], {true}), "string"); CHECK_EQ(toString(ttvC->instantiatedTypePackParams[0], {true}), "number, boolean"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_import") { fileResolver.source["game/A"] = R"( export type Packed<T, U...> = { a: T, b: (U...) -> () } return {} )"; CheckResult aResult = frontend.check("game/A"); LUAU_REQUIRE_NO_ERRORS(aResult); CheckResult bResult = check(R"( local Import = require(game.A) local a: Import.Packed<number> local b: Import.Packed<string, number> local c: Import.Packed<string, number, boolean> local d: { a: typeof(c) } )"); LUAU_REQUIRE_NO_ERRORS(bResult); auto tf = lookupImportedType("Import", "Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed<T, U...>"); CHECK_EQ(toString(*tf, {true}), "{| a: T, b: (U...) -> () |}"); CHECK_EQ(toString(requireType("a"), {true}), "{| a: number, b: () -> () |}"); CHECK_EQ(toString(requireType("b"), {true}), "{| a: string, b: (number) -> () |}"); CHECK_EQ(toString(requireType("c"), {true}), "{| a: string, b: (number, boolean) -> () |}"); CHECK_EQ(toString(requireType("d")), "{| a: Packed<string, number, boolean> |}"); } TEST_CASE_FIXTURE(Fixture, "type_pack_type_parameters") { fileResolver.source["game/A"] = R"( export type Packed<T, U...> = { a: T, b: (U...) -> () } return {} )"; CheckResult cResult = check(R"( local Import = require(game.A) type Alias<S, T, R...> = Import.Packed<S, (T, R...)> local a: Alias<string, number, boolean> type B<X...> = Import.Packed<string, X...> type C<X...> = Import.Packed<string, (number, X...)> )"); LUAU_REQUIRE_NO_ERRORS(cResult); auto tf = lookupType("Alias"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Alias<S, T, R...>"); CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}"); CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}"); tf = lookupType("B"); REQUIRE(tf); CHECK_EQ(toString(*tf), "B<X...>"); CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}"); tf = lookupType("C"); REQUIRE(tf); CHECK_EQ(toString(*tf), "C<X...>"); CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (number, X...) -> () |}"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested") { CheckResult result = check(R"( type Packed1<T...> = (T...) -> (T...) type Packed2<T...> = (Packed1<T...>, T...) -> (Packed1<T...>, T...) type Packed3<T...> = (Packed2<T...>, T...) -> (Packed2<T...>, T...) type Packed4<T...> = (Packed3<T...>, T...) -> (Packed3<T...>, T...) )"); LUAU_REQUIRE_NO_ERRORS(result); auto tf = lookupType("Packed4"); REQUIRE(tf); CHECK_EQ(toString(*tf), "((((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...) -> (((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...), T...) -> " "((((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...) -> (((T...) -> (T...), T...) -> ((T...) -> (T...), T...), T...), T...)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_variadic") { CheckResult result = check(R"( type X<T...> = (T...) -> (string, T...) type D = X<...number> type E = X<(number, ...string)> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("D")), "(...number) -> (string, ...number)"); CHECK_EQ(toString(*lookupType("E")), "(number, ...string) -> (string, number, ...string)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi") { CheckResult result = check(R"( type Y<T..., U...> = (T...) -> (U...) type A<S...> = Y<S..., S...> type B<S...> = Y<(number, ...string), S...> type Z<T, U...> = (T) -> (U...) type E<S...> = Z<number, S...> type F<S...> = Z<number, (string, S...)> type W<T, U..., V...> = (T, U...) -> (T, V...) type H<S..., R...> = W<number, S..., R...> type I<S..., R...> = W<number, (string, S...), R...> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("A")), "(S...) -> (S...)"); CHECK_EQ(toString(*lookupType("B")), "(number, ...string) -> (S...)"); CHECK_EQ(toString(*lookupType("E")), "(number) -> (S...)"); CHECK_EQ(toString(*lookupType("F")), "(number) -> (string, S...)"); CHECK_EQ(toString(*lookupType("H")), "(number, S...) -> (number, R...)"); CHECK_EQ(toString(*lookupType("I")), "(number, string, S...) -> (number, R...)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit") { CheckResult result = check(R"( type X<T...> = (T...) -> (T...) type A<S...> = X<(S...)> type B = X<()> type C = X<(number)> type D = X<(number, string)> type E = X<(...number)> type F = X<(string, ...number)> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("A")), "(S...) -> (S...)"); CHECK_EQ(toString(*lookupType("B")), "() -> ()"); CHECK_EQ(toString(*lookupType("C")), "(number) -> number"); CHECK_EQ(toString(*lookupType("D")), "(number, string) -> (number, string)"); CHECK_EQ(toString(*lookupType("E")), "(...number) -> (...number)"); CHECK_EQ(toString(*lookupType("F")), "(string, ...number) -> (string, ...number)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi") { CheckResult result = check(R"( type Y<T..., U...> = (T...) -> (U...) type A = Y<(number, string), (boolean)> type B = Y<(), ()> type C<S...> = Y<...string, (number, S...)> type D<X...> = Y<X..., (number, string, X...)> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("A")), "(number, string) -> boolean"); CHECK_EQ(toString(*lookupType("B")), "() -> ()"); CHECK_EQ(toString(*lookupType("C")), "(...string) -> (number, S...)"); CHECK_EQ(toString(*lookupType("D")), "(X...) -> (number, string, X...)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_explicit_multi_tostring") { CheckResult result = check(R"( type Y<T..., U...> = { f: (T...) -> (U...) } local a: Y<(number, string), (boolean)> local b: Y<(), ()> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<(number, string), (boolean)>"); CHECK_EQ(toString(requireType("b")), "Y<(), ()>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_backwards_compatible") { CheckResult result = check(R"( type X<T> = () -> T type Y<T, U> = (T) -> U type A = X<(number)> type B = Y<(number), (boolean)> type C = Y<(number), boolean> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("A")), "() -> number"); CHECK_EQ(toString(*lookupType("B")), "(number) -> boolean"); CHECK_EQ(toString(*lookupType("C")), "(number) -> boolean"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_errors") { CheckResult result = check(R"( type Packed<T, U, V...> = (T, U) -> (V...) local b: Packed<number> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed<T, U, V...>' expects at least 2 type arguments, but only 1 is specified"); result = check(R"( type Packed<T, U> = (T, U) -> () type B<X...> = Packed<number, string, X...> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed<T, U>' expects 0 type pack arguments, but 1 is specified"); result = check(R"( type Packed<T..., U...> = (T...) -> (U...) type Other<S...> = Packed<S..., string> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Type parameters must come before type pack parameters"); result = check(R"( type Packed<T, U> = (T) -> U type Other<S...> = Packed<number, S...> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed<T, U>' expects 2 type arguments, but only 1 is specified"); result = check(R"( type Packed<T...> = (T...) -> T... local a: Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); result = check(R"( type Packed<T..., U...> = (T...) -> (U...) type Other = Packed<> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed<T..., U...>' expects 2 type pack arguments, but none are specified"); result = check(R"( type Packed<T..., U...> = (T...) -> (U...) type Other = Packed<number, string> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed<T..., U...>' expects 2 type pack arguments, but only 1 is specified"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T, U = string> = { a: T, b: U } local a: Y<number, number> = { a = 2, b = 3 } local b: Y<number> = { a = 2, b = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, number>"); CHECK_EQ(toString(requireType("b")), "Y<number, string>"); result = check(R"( type Y<T = string> = { a: T } local a: Y<number> = { a = 2 } local b: Y<> = { a = "s" } local c: Y = { a = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number>"); CHECK_EQ(toString(requireType("b")), "Y<string>"); CHECK_EQ(toString(requireType("c")), "Y<string>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_self") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T, U = T> = { a: T, b: U } local a: Y<number> = { a = 2, b = 3 } local b: Y<string> = { a = "h", b = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, number>"); CHECK_EQ(toString(requireType("b")), "Y<string, string>"); result = check(R"( type Y<T, U = (T, T) -> string> = { a: T, b: U } local a: Y<number> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, (number, number) -> string>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_chained") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T, U = T, V = U> = { a: T, b: U, c: V } local a: Y<number> local b: Y<number, string> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, number, number>"); CHECK_EQ(toString(requireType("b")), "Y<number, string, string>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_explicit") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T... = (string, number)> = { a: (T...) -> () } local a: Y<> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<string, number>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T, U... = ...T> = { a: T, b: (U...) -> T } local a: Y<number> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, ...number>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_tp") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T..., U... = T...> = { a: (T...) -> U... } local a: Y<number, string> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<(number, string), (number, string)>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_chained_tp") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T..., U... = T..., V... = U...> = { a: (T...) -> U..., b: (T...) -> V... } local a: Y<number, string> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<(number, string), (number, string), (number, string)>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_mixed_self") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T, U = T, V... = ...number, W... = (T, U, V...)> = { a: (T, U, V...) -> W... } local a: Y<number> local b: Y<number, string> local c: Y<number, string, ...boolean> local d: Y<number, string, ...boolean, ...() -> ()> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y<number, number, ...number, (number, number, ...number)>"); CHECK_EQ(toString(requireType("b")), "Y<number, string, ...number, (number, string, ...number)>"); CHECK_EQ(toString(requireType("c")), "Y<number, string, ...boolean, (number, string, ...boolean)>"); CHECK_EQ(toString(requireType("d")), "Y<number, string, ...boolean, ...() -> ()>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T = T> = { a: T } local a: Y = { a = 2 } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'"); result = check(R"( type Y<T... = T...> = { a: (T...) -> () } local a: Y<> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Unknown type 'T'"); result = check(R"( type Y<T = string, U... = ...string> = { a: (T) -> U... } local a: Y<...number> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Y<T, U...>' expects at least 1 type argument, but none are specified"); result = check(R"( type Packed<T> = (T) -> T local a: Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); result = check(R"( type Y<T, U = T, V> = { a: T } local a: Y<number> )"); LUAU_REQUIRE_ERRORS(result); result = check(R"( type Y<T..., U... = T..., V...> = { a: T } local a: Y<...number> )"); LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_export") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; fileResolver.source["Module/Types"] = R"( export type A<T, U = string> = { a: T, b: U } export type B<T, U = T> = { a: T, b: U } export type C<T, U = (T, T) -> string> = { a: T, b: U } export type D<T, U = T, V = U> = { a: T, b: U, c: V } export type E<T... = (string, number)> = { a: (T...) -> () } export type F<T, U... = ...T> = { a: T, b: (U...) -> T } export type G<T..., U... = ()> = { b: (U...) -> T... } export type H<T... = ()> = { b: (T...) -> T... } return {} )"; CheckResult resultTypes = frontend.check("Module/Types"); LUAU_REQUIRE_NO_ERRORS(resultTypes); fileResolver.source["Module/Users"] = R"( local Types = require(script.Parent.Types) local a: Types.A<number> local b: Types.B<number> local c: Types.C<number> local d: Types.D<number> local e: Types.E<> local eVoid: Types.E<()> local f: Types.F<number> local g: Types.G<...number> local h: Types.H<> )"; CheckResult resultUsers = frontend.check("Module/Users"); LUAU_REQUIRE_NO_ERRORS(resultUsers); CHECK_EQ(toString(requireType("Module/Users", "a")), "A<number, string>"); CHECK_EQ(toString(requireType("Module/Users", "b")), "B<number, number>"); CHECK_EQ(toString(requireType("Module/Users", "c")), "C<number, (number, number) -> string>"); CHECK_EQ(toString(requireType("Module/Users", "d")), "D<number, number, number>"); CHECK_EQ(toString(requireType("Module/Users", "e")), "E<string, number>"); CHECK_EQ(toString(requireType("Module/Users", "eVoid")), "E<>"); CHECK_EQ(toString(requireType("Module/Users", "f")), "F<number, ...number>"); CHECK_EQ(toString(requireType("Module/Users", "g")), "G<...number, ()>"); CHECK_EQ(toString(requireType("Module/Users", "h")), "H<>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_skip_brackets") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type Y<T... = ...string> = (T...) -> number local a: Y )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "(...string) -> number"); } TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_confusing_types") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type A<T, U = T, V... = ...any, W... = V...> = (T, V...) -> (U, W...) type B = A<string, (number)> type C = A<string, (number), (boolean)> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("B"), {true}), "(string, ...any) -> (number, ...any)"); CHECK_EQ(toString(*lookupType("C"), {true}), "(string, boolean) -> (number, boolean)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type") { ScopedFastFlag luauParseTypeAliasDefaults{"LuauParseTypeAliasDefaults", true}; ScopedFastFlag luauTypeAliasDefaults{"LuauTypeAliasDefaults", true}; CheckResult result = check(R"( type F<K = string, V = (K) -> ()> = (K) -> V type R = { m: F<R> } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}"); } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { ScopedFastFlag luauUnifyPackTails{"LuauUnifyPackTails", true}; CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) a = b )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)' caused by: Type 'boolean' could not be converted into 'string')"); } TEST_SUITE_END();