// 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/TypeInfer.h" #include "Luau/Type.h" #include "Fixture.h" #include "doctest.h" using namespace Luau; LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(LuauInstantiateInSubtyping); 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 FunctionType* takeTwoType = get(requireType("take_two")); REQUIRE(takeTwoType != nullptr); const auto& [returns, tail] = flatten(takeTwoType->retTypes); CHECK_EQ(2, returns.size()); CHECK_EQ(builtinTypes->numberType, follow(returns[0])); CHECK_EQ(builtinTypes->numberType, follow(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 FunctionType* takeOneMoreType = get(requireType("take_three")); REQUIRE(takeOneMoreType != nullptr); const auto& [rets, tail] = flatten(takeOneMoreType->retTypes); REQUIRE_EQ(3, rets.size()); CHECK_EQ(builtinTypes->numberType, follow(rets[0])); CHECK_EQ(builtinTypes->numberType, follow(rets[1])); CHECK_EQ(builtinTypes->numberType, follow(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); CHECK_EQ("((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); } 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 FunctionType* fTy = get(requireType("f")); REQUIRE(fTy != nullptr); CHECK_EQ(0, size(fTy->retTypes)); const FunctionType* gTy = get(requireType("g")); REQUIRE(gTy != nullptr); CHECK_EQ(0, size(gTy->retTypes)); } 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 FunctionType* fTy = get(requireType("f")); REQUIRE(fTy != nullptr); CHECK_EQ(1, size(follow(fTy->retTypes))); const FunctionType* gTy = get(requireType("g")); REQUIRE(gTy != nullptr); CHECK_EQ(0, size(gTy->retTypes)); const FunctionType* hTy = get(requireType("h")); REQUIRE(hTy != nullptr); CHECK_EQ(0, size(hTy->retTypes)); } 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 = frontend.globals.globalTypes; unfreeze(arena); TypePackId listOfNumbers = arena.addTypePack(TypePackVar{VariadicTypePack{builtinTypes->numberType}}); TypePackId listOfStrings = arena.addTypePack(TypePackVar{VariadicTypePack{builtinTypes->stringType}}); // clang-format off addGlobalBinding(frontend.globals, "foo", arena.addType( FunctionType{ listOfNumbers, arena.addTypePack({builtinTypes->numberType}) } ), "@test" ); addGlobalBinding(frontend.globals, "bar", arena.addType( FunctionType{ arena.addTypePack({{builtinTypes->numberType}, listOfStrings}), arena.addTypePack({builtinTypes->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(Location{Position{3, 21}, Position{3, 26}} == result.errors[0].location); CHECK(Location{Position{4, 29}, Position{4, 30}} == result.errors[1].location); CHECK_EQ( result.errors[0], (TypeError{Location(Position{3, 21}, Position{3, 26}), TypeMismatch{builtinTypes->numberType, builtinTypes->stringType}}) ); CHECK_EQ( result.errors[1], (TypeError{Location(Position{4, 29}, Position{4, 30}), TypeMismatch{builtinTypes->stringType, builtinTypes->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) -> ()"); } #if 0 TEST_CASE_FIXTURE(Fixture, "type_pack_hidden_free_tail_infinite_growth") { CheckResult result = check(R"( --!nonstrict if _ then _[function(l0)end],l0 = _ elseif _ then return l0(nil) elseif 1 / l0(nil) then elseif _ then return #_,l0() end )"); LUAU_REQUIRE_ERRORS(result); } #endif 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... local a: Packed<> local b: Packed local c: Packed )"); 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 = { f: (a: T, U...) -> (T, U...) } local a: Packed local b: Packed local c: Packed )"); LUAU_REQUIRE_NO_ERRORS(result); tf = lookupType("Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(*tf, {true}), "{ f: (T, U...) -> (T, U...) }"); else CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}"); auto ttvA = get(requireType("a")); REQUIRE(ttvA); CHECK_EQ(toString(requireType("a")), "Packed"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireType("a"), {true}), "{ f: (number) -> number }"); else 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(requireType("b")); REQUIRE(ttvB); CHECK_EQ(toString(requireType("b")), "Packed"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireType("b"), {true}), "{ f: (string, number) -> (string, number) }"); else 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(requireType("c")); REQUIRE(ttvC); CHECK_EQ(toString(requireType("c")), "Packed"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireType("c"), {true}), "{ f: (string, number, boolean) -> (string, number, boolean) }"); else 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(BuiltinsFixture, "type_alias_type_packs_import") { fileResolver.source["game/A"] = R"( export type Packed = { 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 local b: Import.Packed local c: Import.Packed local d: { a: typeof(c) } )"); LUAU_REQUIRE_NO_ERRORS(bResult); auto tf = lookupImportedType("Import", "Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed"); if (FFlag::DebugLuauDeferredConstraintResolution) { 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 }"); } else { 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 |}"); } } TEST_CASE_FIXTURE(BuiltinsFixture, "type_pack_type_parameters") { fileResolver.source["game/A"] = R"( export type Packed = { a: T, b: (U...) -> () } return {} )"; CheckResult cResult = check(R"( local Import = require(game.A) type Alias = Import.Packed local a: Alias type B = Import.Packed type C = Import.Packed )"); LUAU_REQUIRE_NO_ERRORS(cResult); auto tf = lookupType("Alias"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Alias"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(*tf, {true}), "{ a: S, b: (T, R...) -> () }"); else CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(requireType("a"), {true}), "{ a: string, b: (number, boolean) -> () }"); else CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}"); tf = lookupType("B"); REQUIRE(tf); CHECK_EQ(toString(*tf), "B"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (X...) -> () }"); else CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}"); tf = lookupType("C"); REQUIRE(tf); CHECK_EQ(toString(*tf), "C"); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (number, X...) -> () }"); else 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...) type Packed2 = (Packed1, T...) -> (Packed1, T...) type Packed3 = (Packed2, T...) -> (Packed2, T...) type Packed4 = (Packed3, T...) -> (Packed3, 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...) -> (string, T...) type D = X<...number> type E = X<(number, ...string)> )"); LUAU_REQUIRE_NO_ERRORS(result); auto d = lookupType("D"); REQUIRE(d); auto e = lookupType("E"); REQUIRE(e); CHECK_EQ(toString(*d), "(...number) -> (string, ...number)"); CHECK_EQ(toString(*e), "(number, ...string) -> (string, number, ...string)"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_pack_multi") { CheckResult result = check(R"( type Y = (T...) -> (U...) type A = Y type B = Y<(number, ...string), S...> type Z = (T) -> (U...) type E = Z type F = Z type W = (T, U...) -> (T, V...) type H = W type I = W )"); 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...) type A = 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...) type A = Y<(number, string), (boolean)> type B = Y<(), ()> type C = Y<...string, (number, S...)> type D = Y )"); 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 = { 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 type Y = (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...) local b: Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects at least 2 type arguments, but only 1 is specified"); result = check(R"( type Packed = (T, U) -> () type B = Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 0 type pack arguments, but 1 is specified"); result = check(R"( type Packed = (T...) -> (U...) type Other = Packed )"); 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 type Other = Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 2 type arguments, but only 1 is specified"); result = check(R"( type Packed = (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...) type Other = Packed<> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 2 type pack arguments, but none are specified"); result = check(R"( type Packed = (T...) -> (U...) type Other = Packed )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 2 type pack arguments, but only 1 is specified"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit") { CheckResult result = check(R"( type Y = { a: T, b: U } local a: Y = { a = 2, b = 3 } local b: Y = { a = 2, b = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); CHECK_EQ(toString(requireType("b")), "Y"); result = check(R"( type Y = { a: T } local a: Y = { a = 2 } local b: Y<> = { a = "s" } local c: Y = { a = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); CHECK_EQ(toString(requireType("b")), "Y"); CHECK_EQ(toString(requireType("c")), "Y"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_self") { CheckResult result = check(R"( type Y = { a: T, b: U } local a: Y = { a = 2, b = 3 } local b: Y = { a = "h", b = "s" } )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); CHECK_EQ(toString(requireType("b")), "Y"); result = check(R"( type Y string> = { a: T, b: U } local a: Y )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y string>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_chained") { CheckResult result = check(R"( type Y = { a: T, b: U, c: V } local a: Y local b: Y )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); CHECK_EQ(toString(requireType("b")), "Y"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_explicit") { CheckResult result = check(R"( type Y = { a: (T...) -> () } local a: Y<> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_ty") { CheckResult result = check(R"( type Y = { a: T, b: (U...) -> T } local a: Y )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_pack_self_tp") { CheckResult result = check(R"( type Y = { a: (T...) -> U... } local a: Y )"); 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") { CheckResult result = check(R"( type Y = { a: (T...) -> U..., b: (T...) -> V... } local a: Y )"); 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") { CheckResult result = check(R"( type Y = { a: (T, U, V...) -> W... } local a: Y local b: Y local c: Y local d: Y ()> )"); LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireType("a")), "Y"); CHECK_EQ(toString(requireType("b")), "Y"); CHECK_EQ(toString(requireType("c")), "Y"); CHECK_EQ(toString(requireType("d")), "Y ()>"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors") { CheckResult result = check(R"( type Y = { 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 = { 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 = { a: (T) -> U... } local a: Y<...number> )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Y' expects at least 1 type argument, but none are specified"); result = check(R"( type Packed = (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 = { a: T } local a: Y )"); LUAU_REQUIRE_ERRORS(result); result = check(R"( type Y = { a: T } local a: Y<...number> )"); LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_default_export") { fileResolver.source["Module/Types"] = R"( export type A = { a: T, b: U } export type B = { a: T, b: U } export type C string> = { a: T, b: U } export type D = { a: T, b: U, c: V } export type E = { a: (T...) -> () } export type F = { a: T, b: (U...) -> T } export type G = { b: (U...) -> T... } export type H = { 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 local b: Types.B local c: Types.C local d: Types.D local e: Types.E<> local eVoid: Types.E<()> local f: Types.F 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"); CHECK_EQ(toString(requireType("Module/Users", "b")), "B"); CHECK_EQ(toString(requireType("Module/Users", "c")), "C string>"); CHECK_EQ(toString(requireType("Module/Users", "d")), "D"); CHECK_EQ(toString(requireType("Module/Users", "e")), "E"); CHECK_EQ(toString(requireType("Module/Users", "eVoid")), "E<>"); CHECK_EQ(toString(requireType("Module/Users", "f")), "F"); 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") { CheckResult result = check(R"( type Y = (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") { CheckResult result = check(R"( type A = (T, V...) -> (U, W...) type B = A type C = A )"); 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") { CheckResult result = check(R"( type F ()> = (K) -> V type R = { m: F } )"); LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = { m: (t1) -> (t1) -> () }"); else CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}"); } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") { CheckResult result = check(R"( local a: () -> (number, ...string) local b: () -> (number, ...boolean) a = b )"); LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type '() -> (number, ...boolean)' could not be converted into '() -> (number, ...string)' caused by: Type 'boolean' could not be converted into 'string')"; CHECK_EQ(expected, toString(result.errors[0])); } // TODO: File a Jira about this /* TEST_CASE_FIXTURE(Fixture, "unifying_vararg_pack_with_fixed_length_pack_produces_fixed_length_pack") { CheckResult result = check(R"( function a(x) return 1 end a(...) )"); LUAU_REQUIRE_NO_ERRORS(result); ModulePtr mainModule = getMainModule(); REQUIRE(mainModule); REQUIRE(mainModule->hasModuleScope()); REQUIRE(bool(mainModule->getModuleScope()->varargPack)); TypePackId varargPack = *mainModule->getModuleScope()->varargPack; auto iter = begin(varargPack); auto endIter = end(varargPack); CHECK(iter != endIter); ++iter; CHECK(iter == endIter); CHECK(!iter.tail()); } */ TEST_CASE_FIXTURE(Fixture, "dont_ice_if_a_TypePack_is_an_error") { CheckResult result = check(R"( --!strict function f(s) print(s) return f end f("foo")("bar") )"); } TEST_CASE_FIXTURE(Fixture, "cyclic_type_packs") { // this has a risk of creating cyclic type packs, causing infinite loops / OOMs check(R"( --!nonstrict _ += _(_,...) repeat _ += _(...) until ... + _ )"); check(R"( --!nonstrict _ += _(_(...,...),_(...)) repeat until _ )"); } TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks") { CheckResult result = check(R"( type ( ... ) ( ) ; ( ... ) ( - - ... ) ( - ... ) type = ( ... ) ; ( ... ) ( ) ( ... ) ; ( ... ) "" )"); LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "detect_cyclic_typepacks2") { CheckResult result = check(R"( function _(l0:((typeof((pcall)))|((((t0)->())|(typeof(-67108864)))|(any)))|(any),...):(((typeof(0))|(any))|(any),typeof(-67108864),any) xpcall(_,_,_) _(_,_,_) end )"); LUAU_REQUIRE_ERROR_COUNT(2, result); CHECK("Unknown type 't0'" == toString(result.errors[0])); CHECK(get(result.errors[1])); } TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments") { CheckResult result = check(R"( function foo(...: string): number return 1 end function bar(...: number): number return foo(...) end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'string'"); } TEST_CASE_FIXTURE(Fixture, "unify_variadic_tails_in_arguments_free") { CheckResult result = check(R"( function foo(...: T...): T... return ... end function bar(...: number): boolean return foo(...) end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::DebugLuauDeferredConstraintResolution) CHECK( toString(result.errors.at(0)) == "Type pack '...number' could not be converted into 'boolean'; type ...number.tail() (...number) is not a subtype of boolean (boolean)" ); else CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'boolean'"); } TEST_CASE_FIXTURE(BuiltinsFixture, "type_packs_with_tails_in_vararg_adjustment") { std::optional sff; if (FFlag::DebugLuauDeferredConstraintResolution) sff = {FFlag::LuauInstantiateInSubtyping, true}; CheckResult result = check(R"( local function wrapReject(fn: (self: any, ...TArg) -> ...TResult): (self: any, ...TArg) -> ...TResult return function(self, ...) local arguments = { ... } local ok, result = pcall(function() return fn(self, table.unpack(arguments)) end) return result end end )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "generalize_expectedTypes_with_proper_scope") { ScopedFastFlag sff[] = { {FFlag::DebugLuauDeferredConstraintResolution, true}, {FFlag::LuauInstantiateInSubtyping, true}, }; CheckResult result = check(R"( local function f(fn: () -> ...TResult): () -> ...TResult return function() end end )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "fuzz_typepack_iter_follow") { CheckResult result = check(R"( local _ local _ = _,_(),_(_) )"); LUAU_REQUIRE_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_typepack_iter_follow_2") { CheckResult result = check(R"( function test(name, searchTerm) local found = string.find(name:lower(), searchTerm:lower()) end )"); LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "type_param_overflow") { CheckResult result = check(R"( type Two = { a: T, b: U } local x: Two = { a = 1, b = 'c' } )"); LUAU_REQUIRE_ERROR_COUNT(1, result); } TEST_SUITE_END();