2021-10-30 04:25:12 +08:00
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2021-11-05 10:07:18 +08:00
# include "Luau/Scope.h"
2021-10-30 04:25:12 +08:00
# include "Luau/ToString.h"
# include "Fixture.h"
2023-01-04 01:33:19 +08:00
# include "ScopedFlags.h"
2021-10-30 04:25:12 +08:00
# include "doctest.h"
using namespace Luau ;
2022-04-15 05:57:15 +08:00
LUAU_FASTFLAG ( LuauRecursiveTypeParameterRestriction ) ;
2023-07-28 19:37:00 +08:00
LUAU_FASTFLAG ( DebugLuauDeferredConstraintResolution ) ;
2024-08-02 07:25:12 +08:00
LUAU_FASTFLAG ( LuauAttributeSyntax ) ;
2024-08-10 00:46:26 +08:00
LUAU_FASTFLAG ( LuauUserDefinedTypeFunctions )
2022-04-15 05:57:15 +08:00
2021-10-30 04:25:12 +08:00
TEST_SUITE_BEGIN ( " ToString " ) ;
TEST_CASE_FIXTURE ( Fixture , " primitive " )
{
CheckResult result = check ( " local a = nil local b = 44 local c = 'lalala' local d = true " ) ;
LUAU_REQUIRE_NO_ERRORS ( result ) ;
2024-08-10 00:46:26 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK ( " nil " = = toString ( requireType ( " a " ) ) ) ;
else
{
// A variable without an annotation and with a nil literal should infer as 'free', not 'nil'
CHECK_NE ( " nil " , toString ( requireType ( " a " ) ) ) ;
}
2021-10-30 04:25:12 +08:00
CHECK_EQ ( " number " , toString ( requireType ( " b " ) ) ) ;
CHECK_EQ ( " string " , toString ( requireType ( " c " ) ) ) ;
CHECK_EQ ( " boolean " , toString ( requireType ( " d " ) ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " bound_types " )
{
CheckResult result = check ( " local a = 444 local b = a " ) ;
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK_EQ ( " number " , toString ( requireType ( " b " ) ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " free_types " )
{
2024-08-10 00:46:26 +08:00
ScopedFastFlag sff { FFlag : : DebugLuauDeferredConstraintResolution , false } ;
2021-10-30 04:25:12 +08:00
CheckResult result = check ( " local a " ) ;
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK_EQ ( " a " , toString ( requireType ( " a " ) ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " cyclic_table " )
{
2023-01-04 01:33:19 +08:00
Type cyclicTable { TypeVariant ( TableType ( ) ) } ;
TableType * tableOne = getMutable < TableType > ( & cyclicTable ) ;
2021-10-30 04:25:12 +08:00
tableOne - > props [ " self " ] = { & cyclicTable } ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " t1 where t1 = {| self: t1 |} " , toString ( & cyclicTable ) ) ;
else
CHECK_EQ ( " t1 where t1 = { self: t1 } " , toString ( & cyclicTable ) ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " named_table " )
{
2023-01-04 01:33:19 +08:00
Type table { TypeVariant ( TableType ( ) ) } ;
TableType * t = getMutable < TableType > ( & table ) ;
2021-10-30 04:25:12 +08:00
t - > name = " TheTable " ;
CHECK_EQ ( " TheTable " , toString ( & table ) ) ;
}
2022-06-17 08:54:42 +08:00
TEST_CASE_FIXTURE ( Fixture , " empty_table " )
{
CheckResult result = check ( R " (
local a : { }
) " );
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " { } " , toString ( requireType ( " a " ) ) ) ;
else
CHECK_EQ ( " {| |} " , toString ( requireType ( " a " ) ) ) ;
2022-06-17 08:54:42 +08:00
// Should stay the same with useLineBreaks enabled
ToStringOptions opts ;
opts . useLineBreaks = true ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " { } " , toString ( requireType ( " a " ) , opts ) ) ;
else
CHECK_EQ ( " {| |} " , toString ( requireType ( " a " ) , opts ) ) ;
2022-06-17 08:54:42 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " table_respects_use_line_break " )
{
CheckResult result = check ( R " (
local a : { prop : string , anotherProp : number , thirdProp : boolean }
) " );
ToStringOptions opts ;
opts . useLineBreaks = true ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" { \n "
" anotherProp: number, \n "
" prop: string, \n "
" thirdProp: boolean \n "
" } " ,
toString ( requireType ( " a " ) , opts )
) ;
2023-09-30 08:22:06 +08:00
else
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" {| \n "
" anotherProp: number, \n "
" prop: string, \n "
" thirdProp: boolean \n "
" |} " ,
toString ( requireType ( " a " ) , opts )
) ;
2022-06-17 08:54:42 +08:00
}
2023-01-04 01:33:19 +08:00
TEST_CASE_FIXTURE ( Fixture , " nil_or_nil_is_nil_not_question_mark " )
{
CheckResult result = check ( R " (
type nil_ty = nil | nil
local a : nil_ty = nil
) " );
ToStringOptions opts ;
opts . useLineBreaks = false ;
CHECK_EQ ( " nil " , toString ( requireType ( " a " ) , opts ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " long_disjunct_of_nil_is_nil_not_question_mark " )
{
CheckResult result = check ( R " (
type nil_ty = nil | nil | nil | nil | nil
local a : nil_ty = nil
) " );
ToStringOptions opts ;
opts . useLineBreaks = false ;
CHECK_EQ ( " nil " , toString ( requireType ( " a " ) , opts ) ) ;
}
2022-07-08 09:05:31 +08:00
TEST_CASE_FIXTURE ( Fixture , " metatable " )
{
2023-01-04 01:33:19 +08:00
Type table { TypeVariant ( TableType ( ) ) } ;
Type metatable { TypeVariant ( TableType ( ) ) } ;
Type mtv { TypeVariant ( MetatableType { & table , & metatable } ) } ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " { @metatable {| |}, {| |} } " , toString ( & mtv ) ) ;
else
CHECK_EQ ( " { @metatable { }, { } } " , toString ( & mtv ) ) ;
2022-07-08 09:05:31 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " named_metatable " )
{
2023-01-04 01:33:19 +08:00
Type table { TypeVariant ( TableType ( ) ) } ;
Type metatable { TypeVariant ( TableType ( ) ) } ;
Type mtv { TypeVariant ( MetatableType { & table , & metatable , " NamedMetatable " } ) } ;
2022-07-08 09:05:31 +08:00
CHECK_EQ ( " NamedMetatable " , toString ( & mtv ) ) ;
}
TEST_CASE_FIXTURE ( BuiltinsFixture , " named_metatable_toStringNamedFunction " )
{
2024-08-10 00:46:26 +08:00
ScopedFastFlag sff { FFlag : : DebugLuauDeferredConstraintResolution , false } ;
2022-07-08 09:05:31 +08:00
CheckResult result = check ( R " (
local function createTbl ( ) : NamedMetatable
return setmetatable ( { } , { } )
end
type NamedMetatable = typeof ( createTbl ( ) )
) " );
TypeId ty = requireType ( " createTbl " ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( ty ) ) ;
2022-07-08 09:05:31 +08:00
REQUIRE ( ftv ) ;
CHECK_EQ ( " createTbl(): NamedMetatable " , toStringNamedFunction ( " createTbl " , * ftv ) ) ;
}
2022-05-14 03:16:50 +08:00
TEST_CASE_FIXTURE ( BuiltinsFixture , " exhaustive_toString_of_cyclic_table " )
2021-10-30 04:25:12 +08:00
{
CheckResult result = check ( R " (
- - ! strict
local Vec3 = { }
Vec3 . __index = Vec3
function Vec3 . new ( )
return setmetatable ( { x = 0 , y = 0 , z = 0 } , Vec3 )
end
export type Vec3 = typeof ( Vec3 . new ( ) )
local thefun : any = function ( self , o ) return self end
local multiply : ( ( Vec3 , Vec3 ) - > Vec3 ) & ( ( Vec3 , number ) - > Vec3 ) = thefun
Vec3 . __mul = multiply
local a = Vec3 . new ( )
) " );
std : : string a = toString ( requireType ( " a " ) , { true } ) ;
CHECK_EQ ( std : : string : : npos , a . find ( " CYCLE " ) ) ;
CHECK_EQ ( std : : string : : npos , a . find ( " TRUNCATED " ) ) ;
2024-08-10 00:46:26 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
{
CHECK (
" t2 where "
" t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; "
" t2 = { @metatable t1, { x: number, y: number, z: number } } " = =
a
) ;
}
else
{
CHECK_EQ (
" t2 where "
" t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; "
" t2 = { @metatable t1, {| x: number, y: number, z: number |} } " ,
a
) ;
}
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " intersection_parenthesized_only_if_needed " )
{
2023-03-11 03:20:04 +08:00
auto utv = Type { UnionType { { builtinTypes - > numberType , builtinTypes - > stringType } } } ;
auto itv = Type { IntersectionType { { & utv , builtinTypes - > booleanType } } } ;
2021-10-30 04:25:12 +08:00
CHECK_EQ ( toString ( & itv ) , " (number | string) & boolean " ) ;
}
TEST_CASE_FIXTURE ( Fixture , " union_parenthesized_only_if_needed " )
{
2023-03-11 03:20:04 +08:00
auto itv = Type { IntersectionType { { builtinTypes - > numberType , builtinTypes - > stringType } } } ;
auto utv = Type { UnionType { { & itv , builtinTypes - > booleanType } } } ;
2021-10-30 04:25:12 +08:00
CHECK_EQ ( toString ( & utv ) , " (number & string) | boolean " ) ;
}
TEST_CASE_FIXTURE ( Fixture , " functions_are_always_parenthesized_in_unions_or_intersections " )
{
2023-03-11 03:20:04 +08:00
auto stringAndNumberPack = TypePackVar { TypePack { { builtinTypes - > stringType , builtinTypes - > numberType } } } ;
auto numberAndStringPack = TypePackVar { TypePack { { builtinTypes - > numberType , builtinTypes - > stringType } } } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
auto sn2ns = Type { FunctionType { & stringAndNumberPack , & numberAndStringPack } } ;
2023-03-11 03:20:04 +08:00
auto ns2sn = Type { FunctionType ( frontend . globals . globalScope - > level , & numberAndStringPack , & stringAndNumberPack ) } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
auto utv = Type { UnionType { { & ns2sn , & sn2ns } } } ;
auto itv = Type { IntersectionType { { & ns2sn , & sn2ns } } } ;
2021-10-30 04:25:12 +08:00
CHECK_EQ ( toString ( & utv ) , " ((number, string) -> (string, number)) | ((string, number) -> (number, string)) " ) ;
CHECK_EQ ( toString ( & itv ) , " ((number, string) -> (string, number)) & ((string, number) -> (number, string)) " ) ;
}
2023-10-21 04:36:26 +08:00
TEST_CASE_FIXTURE ( Fixture , " simple_intersections_printed_on_one_line " )
2022-05-20 07:46:52 +08:00
{
2023-10-21 04:36:26 +08:00
CheckResult result = check ( R " (
local a : string & number
) " );
ToStringOptions opts ;
opts . useLineBreaks = true ;
CHECK_EQ ( " number & string " , toString ( requireType ( " a " ) , opts ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " complex_intersections_printed_on_multiple_lines " )
{
CheckResult result = check ( R " (
local a : string & number & boolean
) " );
ToStringOptions opts ;
opts . useLineBreaks = true ;
opts . compositeTypesSingleLineLimit = 2 ;
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" boolean \n "
" & number \n "
" & string " ,
toString ( requireType ( " a " ) , opts )
) ;
2023-10-21 04:36:26 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " overloaded_functions_always_printed_on_multiple_lines " )
{
2022-05-20 07:46:52 +08:00
CheckResult result = check ( R " (
local a : ( ( string ) - > string ) & ( ( number ) - > number )
) " );
ToStringOptions opts ;
opts . useLineBreaks = true ;
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" ((number) -> number) \n "
" & ((string) -> string) " ,
toString ( requireType ( " a " ) , opts )
) ;
2022-05-20 07:46:52 +08:00
}
2023-10-21 04:36:26 +08:00
TEST_CASE_FIXTURE ( Fixture , " simple_unions_printed_on_one_line " )
2022-05-20 07:46:52 +08:00
{
2023-10-21 04:36:26 +08:00
CheckResult result = check ( R " (
local a : number | boolean
) " );
ToStringOptions opts ;
opts . useLineBreaks = true ;
CHECK_EQ ( " boolean | number " , toString ( requireType ( " a " ) , opts ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " complex_unions_printed_on_multiple_lines " )
{
2022-05-20 07:46:52 +08:00
CheckResult result = check ( R " (
local a : string | number | boolean
) " );
ToStringOptions opts ;
2023-10-21 04:36:26 +08:00
opts . compositeTypesSingleLineLimit = 2 ;
2022-05-20 07:46:52 +08:00
opts . useLineBreaks = true ;
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" boolean \n "
" | number \n "
" | string " ,
toString ( requireType ( " a " ) , opts )
) ;
2022-05-20 07:46:52 +08:00
}
2021-10-30 04:25:12 +08:00
TEST_CASE_FIXTURE ( Fixture , " quit_stringifying_table_type_when_length_is_exceeded " )
{
2023-01-04 01:33:19 +08:00
TableType ttv { } ;
2021-10-30 04:25:12 +08:00
for ( char c : std : : string ( " abcdefghijklmno " ) )
2023-03-11 03:20:04 +08:00
ttv . props [ std : : string ( 1 , c ) ] = { builtinTypes - > numberType } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
Type tv { ttv } ;
2021-10-30 04:25:12 +08:00
ToStringOptions o ;
o . exhaustive = false ;
o . maxTableLength = 40 ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( toString ( & tv , o ) , " {| a: number, b: number, c: number, d: number, e: number, ... 10 more ... |} " ) ;
else
CHECK_EQ ( toString ( & tv , o ) , " { a: number, b: number, c: number, d: number, e: number, ... 10 more ... } " ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_table_type_is_still_capped_when_exhaustive " )
{
2023-01-04 01:33:19 +08:00
TableType ttv { } ;
2021-10-30 04:25:12 +08:00
for ( char c : std : : string ( " abcdefg " ) )
2023-03-11 03:20:04 +08:00
ttv . props [ std : : string ( 1 , c ) ] = { builtinTypes - > numberType } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
Type tv { ttv } ;
2021-10-30 04:25:12 +08:00
ToStringOptions o ;
o . exhaustive = true ;
o . maxTableLength = 40 ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( toString ( & tv , o ) , " {| a: number, b: number, c: number, d: number, e: number, ... 2 more ... |} " ) ;
else
CHECK_EQ ( toString ( & tv , o ) , " { a: number, b: number, c: number, d: number, e: number, ... 2 more ... } " ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " quit_stringifying_type_when_length_is_exceeded " )
{
CheckResult result = check ( R " (
function f0 ( ) end
function f1 ( f ) return f or f0 end
function f2 ( f ) return f or f1 end
function f3 ( f ) return f or f2 end
) " );
2022-11-11 06:04:44 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
{
2024-06-15 00:38:56 +08:00
LUAU_REQUIRE_NO_ERRORS ( result ) ;
2024-05-11 00:17:09 +08:00
ToStringOptions o ;
o . exhaustive = false ;
o . maxTypeLength = 20 ;
2022-11-11 06:04:44 +08:00
CHECK_EQ ( toString ( requireType ( " f0 " ) , o ) , " () -> () " ) ;
2024-06-15 00:38:56 +08:00
CHECK_EQ ( toString ( requireType ( " f1 " ) , o ) , " <a>(a) -> (() -> ()) ... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f2 " ) , o ) , " <b>(b) -> (<a>(a) -> (() -> ())... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f3 " ) , o ) , " <c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ())... *TRUNCATED* " ) ;
2022-11-11 06:04:44 +08:00
}
else
{
2024-05-11 00:17:09 +08:00
LUAU_REQUIRE_NO_ERRORS ( result ) ;
ToStringOptions o ;
o . exhaustive = false ;
2022-11-11 06:04:44 +08:00
o . maxTypeLength = 40 ;
CHECK_EQ ( toString ( requireType ( " f0 " ) , o ) , " () -> () " ) ;
CHECK_EQ ( toString ( requireType ( " f1 " ) , o ) , " (() -> ()) -> () -> () " ) ;
CHECK_EQ ( toString ( requireType ( " f2 " ) , o ) , " ((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f3 " ) , o ) , " (((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED* " ) ;
}
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_type_is_still_capped_when_exhaustive " )
{
CheckResult result = check ( R " (
function f0 ( ) end
function f1 ( f ) return f or f0 end
function f2 ( f ) return f or f1 end
function f3 ( f ) return f or f2 end
) " );
2022-11-11 06:04:44 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
{
2024-06-15 00:38:56 +08:00
LUAU_REQUIRE_NO_ERRORS ( result ) ;
2024-05-11 00:17:09 +08:00
ToStringOptions o ;
o . exhaustive = true ;
o . maxTypeLength = 20 ;
2022-11-11 06:04:44 +08:00
CHECK_EQ ( toString ( requireType ( " f0 " ) , o ) , " () -> () " ) ;
2024-06-15 00:38:56 +08:00
CHECK_EQ ( toString ( requireType ( " f1 " ) , o ) , " <a>(a) -> (() -> ()) ... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f2 " ) , o ) , " <b>(b) -> (<a>(a) -> (() -> ())... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f3 " ) , o ) , " <c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ())... *TRUNCATED* " ) ;
2022-11-11 06:04:44 +08:00
}
else
{
2024-05-11 00:17:09 +08:00
LUAU_REQUIRE_NO_ERRORS ( result ) ;
ToStringOptions o ;
o . exhaustive = true ;
2022-11-11 06:04:44 +08:00
o . maxTypeLength = 40 ;
CHECK_EQ ( toString ( requireType ( " f0 " ) , o ) , " () -> () " ) ;
CHECK_EQ ( toString ( requireType ( " f1 " ) , o ) , " (() -> ()) -> () -> () " ) ;
CHECK_EQ ( toString ( requireType ( " f2 " ) , o ) , " ((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED* " ) ;
CHECK_EQ ( toString ( requireType ( " f3 " ) , o ) , " (((() -> ()) -> () -> ()) -> (() -> ()) -> ... *TRUNCATED* " ) ;
}
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_table_type_correctly_use_matching_table_state_braces " )
{
2023-01-04 01:33:19 +08:00
TableType ttv { TableState : : Sealed , TypeLevel { } } ;
2021-10-30 04:25:12 +08:00
for ( char c : std : : string ( " abcdefghij " ) )
2023-03-11 03:20:04 +08:00
ttv . props [ std : : string ( 1 , c ) ] = { builtinTypes - > numberType } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
Type tv { ttv } ;
2021-10-30 04:25:12 +08:00
2021-11-19 06:21:07 +08:00
ToStringOptions o ;
o . maxTableLength = 40 ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( toString ( & tv , o ) , " { a: number, b: number, c: number, d: number, e: number, ... 5 more ... } " ) ;
else
CHECK_EQ ( toString ( & tv , o ) , " {| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |} " ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_cyclic_union_type_bails_early " )
{
2023-03-11 03:20:04 +08:00
Type tv { UnionType { { builtinTypes - > stringType , builtinTypes - > numberType } } } ;
2023-01-04 01:33:19 +08:00
UnionType * utv = getMutable < UnionType > ( & tv ) ;
2021-10-30 04:25:12 +08:00
utv - > options . push_back ( & tv ) ;
utv - > options . push_back ( & tv ) ;
CHECK_EQ ( " t1 where t1 = number | string " , toString ( & tv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_cyclic_intersection_type_bails_early " )
{
2023-01-04 01:33:19 +08:00
Type tv { IntersectionType { } } ;
IntersectionType * itv = getMutable < IntersectionType > ( & tv ) ;
2021-10-30 04:25:12 +08:00
itv - > parts . push_back ( & tv ) ;
itv - > parts . push_back ( & tv ) ;
CHECK_EQ ( " t1 where t1 = t1 & t1 " , toString ( & tv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " stringifying_array_uses_array_syntax " )
{
2023-01-04 01:33:19 +08:00
TableType ttv { TableState : : Sealed , TypeLevel { } } ;
2023-03-11 03:20:04 +08:00
ttv . indexer = TableIndexer { builtinTypes - > numberType , builtinTypes - > stringType } ;
2021-10-30 04:25:12 +08:00
2023-01-04 01:33:19 +08:00
CHECK_EQ ( " {string} " , toString ( Type { ttv } ) ) ;
2021-10-30 04:25:12 +08:00
2023-03-11 03:20:04 +08:00
ttv . props [ " A " ] = { builtinTypes - > numberType } ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " { [number]: string, A: number } " , toString ( Type { ttv } ) ) ;
else
CHECK_EQ ( " {| [number]: string, A: number |} " , toString ( Type { ttv } ) ) ;
2021-10-30 04:25:12 +08:00
ttv . props . clear ( ) ;
ttv . state = TableState : : Unsealed ;
2023-01-04 01:33:19 +08:00
CHECK_EQ ( " {string} " , toString ( Type { ttv } ) ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " generic_packs_are_stringified_differently_from_generic_types " )
{
TypePackVar tpv { GenericTypePack { " a " } } ;
CHECK_EQ ( toString ( & tpv ) , " a... " ) ;
2023-01-04 01:33:19 +08:00
Type tv { GenericType { " a " } } ;
2021-10-30 04:25:12 +08:00
CHECK_EQ ( toString ( & tv ) , " a " ) ;
}
TEST_CASE_FIXTURE ( Fixture , " function_type_with_argument_names " )
{
CheckResult result = check ( " type MyFunc = (a: number, string, c: number) -> string; local a : MyFunc " ) ;
LUAU_REQUIRE_NO_ERRORS ( result ) ;
ToStringOptions opts ;
opts . functionTypeArguments = true ;
CHECK_EQ ( " (a: number, string, c: number) -> string " , toString ( requireType ( " a " ) , opts ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " function_type_with_argument_names_generic " )
{
CheckResult result = check ( " local function f<a...>(n: number, ...: a...): (a...) return ... end " ) ;
LUAU_REQUIRE_NO_ERRORS ( result ) ;
ToStringOptions opts ;
opts . functionTypeArguments = true ;
CHECK_EQ ( " <a...>(n: number, a...) -> (a...) " , toString ( requireType ( " f " ) , opts ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " function_type_with_argument_names_and_self " )
{
CheckResult result = check ( R " (
local tbl = { }
tbl . a = 2
function tbl : foo ( b : number , c : number ) return ( self . a : : number ) + b + c end
type Table = typeof ( tbl )
type Foo = typeof ( tbl . foo )
local u : Foo
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
ToStringOptions opts ;
opts . functionTypeArguments = true ;
// Can't guess the name of 'self' to compare name, but at least there should be no assertion
toString ( requireType ( " u " ) , opts ) ;
}
TEST_CASE_FIXTURE ( Fixture , " generate_friendly_names_for_inferred_generics " )
{
CheckResult result = check ( R " (
function id ( x ) return x end
function id2 ( a1 , a2 , a3 , a4 , a5 , a6 , a7 , a8 , a9 , a10 , a11 , a12 , a13 , a14 , a15 , a16 , a17 , a18 , a19 , a20 , a21 , a22 , a23 , a24 , a25 , a26 , a27 , a28 , a29 , a30 )
return a1 , a2 , a3 , a4 , a5 , a6 , a7 , a8 , a9 , a10 , a11 , a12 , a13 , a14 , a15 , a16 , a17 , a18 , a19 , a20 , a21 , a22 , a23 , a24 , a25 , a26 , a27 , a28 , a29 , a30
end
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK_EQ ( " <a>(a) -> a " , toString ( requireType ( " id " ) ) ) ;
2024-08-02 07:25:12 +08:00
CHECK_EQ (
" <a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, a1, b1, c1, d1>(a, b, c, d, e, f, g, h, i, j, k, l, "
" m, n, o, p, q, r, s, t, u, v, w, x, y, z, a1, b1, c1, d1) -> (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, "
" x, y, z, a1, b1, c1, d1) " ,
toString ( requireType ( " id2 " ) )
) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " toStringDetailed " )
{
CheckResult result = check ( R " (
function id3 ( a , b , c )
return a , b , c
end
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
2022-08-26 04:55:08 +08:00
ToStringOptions opts ;
2021-10-30 04:25:12 +08:00
TypeId id3Type = requireType ( " id3 " ) ;
2022-08-26 04:55:08 +08:00
ToStringResult nameData = toStringDetailed ( id3Type , opts ) ;
2023-01-04 01:33:19 +08:00
REQUIRE ( 3 = = opts . nameMap . types . size ( ) ) ;
2021-10-30 04:25:12 +08:00
REQUIRE_EQ ( " <a, b, c>(a, b, c) -> (a, b, c) " , nameData . name ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( id3Type ) ) ;
2021-10-30 04:25:12 +08:00
REQUIRE ( ftv ! = nullptr ) ;
auto params = flatten ( ftv - > argTypes ) . first ;
2022-08-26 04:55:08 +08:00
REQUIRE ( 3 = = params . size ( ) ) ;
2021-10-30 04:25:12 +08:00
2022-11-11 06:04:44 +08:00
CHECK ( " a " = = toString ( params [ 0 ] , opts ) ) ;
CHECK ( " b " = = toString ( params [ 1 ] , opts ) ) ;
CHECK ( " c " = = toString ( params [ 2 ] , opts ) ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " toStringErrorPack " )
{
2024-08-10 00:46:26 +08:00
ScopedFastFlag sff { FFlag : : DebugLuauDeferredConstraintResolution , false } ;
2021-10-30 04:25:12 +08:00
CheckResult result = check ( R " (
local function target ( callback : nil ) return callback ( 4 , " hello " ) end
) " );
LUAU_REQUIRE_ERRORS ( result ) ;
2022-11-05 01:02:37 +08:00
CHECK_EQ ( " (nil) -> (*error-type*) " , toString ( requireType ( " target " ) ) ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " toStringGenericPack " )
{
CheckResult result = check ( R " (
function foo ( a , b ) return a ( b ) end
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK_EQ ( toString ( requireType ( " foo " ) ) , " <a, b...>((a) -> (b...), a) -> (b...) " ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toString_the_boundTo_table_type_contained_within_a_TypePack " )
{
2023-01-04 01:33:19 +08:00
Type tv1 { TableType { } } ;
TableType * ttv = getMutable < TableType > ( & tv1 ) ;
2021-10-30 04:25:12 +08:00
ttv - > state = TableState : : Sealed ;
2023-03-11 03:20:04 +08:00
ttv - > props [ " hello " ] = { builtinTypes - > numberType } ;
ttv - > props [ " world " ] = { builtinTypes - > numberType } ;
2021-10-30 04:25:12 +08:00
TypePackVar tpv1 { TypePack { { & tv1 } } } ;
2023-01-04 01:33:19 +08:00
Type tv2 { TableType { } } ;
TableType * bttv = getMutable < TableType > ( & tv2 ) ;
2021-10-30 04:25:12 +08:00
bttv - > state = TableState : : Free ;
2023-03-11 03:20:04 +08:00
bttv - > props [ " hello " ] = { builtinTypes - > numberType } ;
2021-10-30 04:25:12 +08:00
bttv - > boundTo = & tv1 ;
TypePackVar tpv2 { TypePack { { & tv2 } } } ;
2023-09-30 08:22:06 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
{
CHECK_EQ ( " { hello: number, world: number } " , toString ( & tpv1 ) ) ;
CHECK_EQ ( " { hello: number, world: number } " , toString ( & tpv2 ) ) ;
}
else
{
CHECK_EQ ( " {| hello: number, world: number |} " , toString ( & tpv1 ) ) ;
CHECK_EQ ( " {| hello: number, world: number |} " , toString ( & tpv2 ) ) ;
}
2021-10-30 04:25:12 +08:00
}
2022-10-14 06:59:53 +08:00
TEST_CASE_FIXTURE ( Fixture , " no_parentheses_around_return_type_if_pack_has_an_empty_head_link " )
{
TypeArena arena ;
2023-01-04 01:33:19 +08:00
TypePackId realTail = arena . addTypePack ( { builtinTypes - > stringType } ) ;
2022-10-14 06:59:53 +08:00
TypePackId emptyTail = arena . addTypePack ( { } , realTail ) ;
2023-01-04 01:33:19 +08:00
TypePackId argList = arena . addTypePack ( { builtinTypes - > stringType } ) ;
2022-10-14 06:59:53 +08:00
2023-01-04 01:33:19 +08:00
TypeId functionType = arena . addType ( FunctionType { argList , emptyTail } ) ;
2022-10-14 06:59:53 +08:00
2022-12-02 18:46:05 +08:00
CHECK ( " (string) -> string " = = toString ( functionType ) ) ;
2022-10-14 06:59:53 +08:00
}
2021-10-30 04:25:12 +08:00
TEST_CASE_FIXTURE ( Fixture , " no_parentheses_around_cyclic_function_type_in_union " )
{
CheckResult result = check ( R " (
type F = ( ( ( ) - > number ) ? ) - > F ?
local function f ( p ) return f end
local g : F = f
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK_EQ ( " t1 where t1 = ((() -> number)?) -> t1? " , toString ( requireType ( " g " ) ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " no_parentheses_around_cyclic_function_type_in_intersection " )
{
CheckResult result = check ( R " (
function f ( ) return f end
local a : ( ( number ) - > ( ) ) & typeof ( f )
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
2024-08-10 00:46:26 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK ( " (() -> t1) & ((number) -> ()) where t1 = () -> t1 " = = toString ( requireType ( " a " ) ) ) ;
else
CHECK_EQ ( " ((number) -> ()) & t1 where t1 = () -> t1 " , toString ( requireType ( " a " ) ) ) ;
2021-10-30 04:25:12 +08:00
}
TEST_CASE_FIXTURE ( Fixture , " self_recursive_instantiated_param " )
{
2023-01-04 01:33:19 +08:00
Type tableTy { TableType { } } ;
TableType * ttv = getMutable < TableType > ( & tableTy ) ;
2021-10-30 04:25:12 +08:00
ttv - > name = " Table " ;
ttv - > instantiatedTypeParams . push_back ( & tableTy ) ;
CHECK_EQ ( toString ( tableTy ) , " Table<Table> " ) ;
}
2021-11-19 06:21:07 +08:00
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_id " )
{
CheckResult result = check ( R " (
local function id ( x ) return x end
) " );
TypeId ty = requireType ( " id " ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " id<a>(x: a): a " , toStringNamedFunction ( " id " , * ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_map " )
{
CheckResult result = check ( R " (
local function map ( arr , fn )
local t = { }
for i = 0 , # arr do
t [ i ] = fn ( arr [ i ] )
end
return t
end
) " );
TypeId ty = requireType ( " map " ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
2024-06-01 01:46:33 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK_EQ ( " map<a, b>(arr: {a}, fn: (a) -> (b, ...unknown)): {b} " , toStringNamedFunction ( " map " , * ftv ) ) ;
else
CHECK_EQ ( " map<a, b>(arr: {a}, fn: (a) -> b): {b} " , toStringNamedFunction ( " map " , * ftv ) ) ;
2021-11-19 06:21:07 +08:00
}
2022-01-15 00:06:31 +08:00
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_generic_pack " )
{
CheckResult result = check ( R " (
local function f ( a : number , b : string ) end
local function test < T . . . , U . . . > ( . . . : T . . . ) : U . . .
f ( . . . )
return 1 , 2 , 3
end
) " );
TypeId ty = requireType ( " test " ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( ty ) ) ;
2022-01-15 00:06:31 +08:00
CHECK_EQ ( " test<T..., U...>(...: T...): U... " , toStringNamedFunction ( " test " , * ftv ) ) ;
}
2021-11-19 06:21:07 +08:00
TEST_CASE ( " toStringNamedFunction_unit_f " )
{
TypePackVar empty { TypePack { } } ;
2023-01-04 01:33:19 +08:00
FunctionType ftv { & empty , & empty , { } , false } ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " f(): () " , toStringNamedFunction ( " f " , ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_variadics " )
{
CheckResult result = check ( R " (
local function f < a , b . . . > ( x : a , . . . ) : ( a , a , b . . . )
return x , x , . . .
end
) " );
TypeId ty = requireType ( " f " ) ;
2023-01-04 01:33:19 +08:00
auto ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " f<a, b...>(x: a, ...: any): (a, a, b...) " , toStringNamedFunction ( " f " , * ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_variadics2 " )
{
CheckResult result = check ( R " (
local function f ( ) : . . . number
return 1 , 2 , 3
end
) " );
TypeId ty = requireType ( " f " ) ;
2023-01-04 01:33:19 +08:00
auto ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " f(): ...number " , toStringNamedFunction ( " f " , * ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_variadics3 " )
{
CheckResult result = check ( R " (
local function f ( ) : ( string , . . . number )
return ' a ' , 1 , 2 , 3
end
) " );
TypeId ty = requireType ( " f " ) ;
2023-01-04 01:33:19 +08:00
auto ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " f(): (string, ...number) " , toStringNamedFunction ( " f " , * ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_type_annotation_has_partial_argnames " )
{
CheckResult result = check ( R " (
local f : ( number , y : number ) - > number
) " );
TypeId ty = requireType ( " f " ) ;
2023-01-04 01:33:19 +08:00
auto ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
CHECK_EQ ( " f(_: number, y: number): number " , toStringNamedFunction ( " f " , * ftv ) ) ;
}
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_hide_type_params " )
{
CheckResult result = check ( R " (
local function f < T > ( x : T , g : < U > ( T ) - > U ) ) : ( )
end
) " );
TypeId ty = requireType ( " f " ) ;
2023-01-04 01:33:19 +08:00
auto ftv = get < FunctionType > ( follow ( ty ) ) ;
2021-11-19 06:21:07 +08:00
ToStringOptions opts ;
opts . hideNamedFunctionTypeParameters = true ;
CHECK_EQ ( " f(x: T, g: <U>(T) -> U): () " , toStringNamedFunction ( " f " , * ftv , opts ) ) ;
}
2022-03-25 05:49:08 +08:00
TEST_CASE_FIXTURE ( Fixture , " toStringNamedFunction_overrides_param_names " )
{
CheckResult result = check ( R " (
local function test ( a , b : string , . . . : number ) return a end
) " );
TypeId ty = requireType ( " test " ) ;
2023-01-04 01:33:19 +08:00
const FunctionType * ftv = get < FunctionType > ( follow ( ty ) ) ;
2022-03-25 05:49:08 +08:00
ToStringOptions opts ;
opts . namedFunctionOverrideArgNames = { " first " , " second " , " third " } ;
CHECK_EQ ( " test<a>(first: a, second: string, ...: number): a " , toStringNamedFunction ( " test " , * ftv , opts ) ) ;
}
2022-05-20 07:46:52 +08:00
TEST_CASE_FIXTURE ( Fixture , " pick_distinct_names_for_mixed_explicit_and_implicit_generics " )
{
CheckResult result = check ( R " (
function foo < a > ( x : a , y ) end
) " );
2024-08-10 00:46:26 +08:00
if ( FFlag : : DebugLuauDeferredConstraintResolution )
{
CHECK ( " <a>(a, 'b) -> () " = = toString ( requireType ( " foo " ) ) ) ;
}
else
CHECK ( " <a, b>(a, b) -> () " = = toString ( requireType ( " foo " ) ) ) ;
2022-05-20 07:46:52 +08:00
}
2022-09-30 06:11:54 +08:00
TEST_CASE_FIXTURE ( Fixture , " tostring_unsee_ttv_if_array " )
{
CheckResult result = check ( R " (
local x : { string }
- - This code is constructed very specifically to use the same ( by pointer
- - identity ) type in the function twice .
local y : ( typeof ( x ) , typeof ( x ) ) - > ( )
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK ( toString ( requireType ( " y " ) ) = = " ({string}, {string}) -> () " ) ;
}
2023-08-11 20:55:30 +08:00
TEST_CASE_FIXTURE ( Fixture , " tostring_error_mismatch " )
{
CheckResult result = check ( R " (
2024-02-24 02:40:00 +08:00
- - ! strict
function f1 ( ) : { a : number , b : string , c : { d : number } }
2024-03-23 01:21:27 +08:00
return { a = 1 , b = " b " , c = { d = " d " } }
2024-02-24 02:40:00 +08:00
end
) " );
2023-08-11 20:55:30 +08:00
2024-02-24 02:40:00 +08:00
std : : string expected ;
if ( FFlag : : DebugLuauDeferredConstraintResolution )
2024-04-20 05:04:30 +08:00
expected =
R " (Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read " c " ][read " d " ], string is not exactly number) " ;
2024-02-24 02:40:00 +08:00
else
expected = R " (Type
2023-08-11 20:55:30 +08:00
' { a : number , b : string , c : { d : string } } '
could not be converted into
' { | a : number , b : string , c : { | d : number | } | } '
caused by :
2023-10-14 03:38:31 +08:00
Property ' c ' is not compatible .
2023-08-11 20:55:30 +08:00
Type
' { d : string } '
could not be converted into
' { | d : number | } '
caused by :
2023-10-14 03:38:31 +08:00
Property ' d ' is not compatible .
2023-08-11 20:55:30 +08:00
Type ' string ' could not be converted into ' number ' in an invariant context ) " ;
LUAU_REQUIRE_ERROR_COUNT ( 1 , result ) ;
2023-09-30 08:22:06 +08:00
2024-02-03 02:20:03 +08:00
std : : string actual = toString ( result . errors [ 0 ] ) ;
2023-08-11 20:55:30 +08:00
CHECK ( expected = = actual ) ;
}
2023-10-21 04:36:26 +08:00
TEST_CASE_FIXTURE ( Fixture , " checked_fn_toString " )
{
ScopedFastFlag flags [ ] = {
2023-12-02 10:04:44 +08:00
{ FFlag : : DebugLuauDeferredConstraintResolution , true } ,
2023-10-21 04:36:26 +08:00
} ;
auto _result = loadDefinition ( R " (
2024-06-08 01:09:03 +08:00
@ checked declare function abs ( n : number ) : number
2023-10-21 04:36:26 +08:00
) " );
auto result = check ( Mode : : Nonstrict , R " (
local f = abs
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
TypeId fn = requireType ( " f " ) ;
CHECK ( " @checked (number) -> number " = = toString ( fn ) ) ;
}
2024-02-24 02:40:00 +08:00
TEST_CASE_FIXTURE ( Fixture , " read_only_properties " )
{
ScopedFastFlag sff { FFlag : : DebugLuauDeferredConstraintResolution , true } ;
CheckResult result = check ( R " (
type A = { x : string }
type B = { read x : string }
) " );
LUAU_REQUIRE_NO_ERRORS ( result ) ;
CHECK ( " { x: string } " = = toString ( requireTypeAlias ( " A " ) , { true } ) ) ;
CHECK ( " { read x: string } " = = toString ( requireTypeAlias ( " B " ) , { true } ) ) ;
}
2024-03-31 06:49:03 +08:00
TEST_CASE_FIXTURE ( Fixture , " cycle_rooted_in_a_pack " )
{
TypeArena arena ;
TypePackId thePack = arena . addTypePack ( { builtinTypes - > numberType , builtinTypes - > numberType } ) ;
TypePack * packPtr = getMutable < TypePack > ( thePack ) ;
REQUIRE ( packPtr ) ;
2024-08-02 07:25:12 +08:00
const TableType : : Props theProps = {
{ " BaseField " , Property : : readonly ( builtinTypes - > unknownType ) } ,
{ " BaseMethod " , Property : : readonly ( arena . addType ( FunctionType { thePack , arena . addTypePack ( { } ) } ) ) }
} ;
2024-03-31 06:49:03 +08:00
TypeId theTable = arena . addType ( TableType { theProps , { } , TypeLevel { } , TableState : : Sealed } ) ;
packPtr - > head [ 0 ] = theTable ;
if ( FFlag : : DebugLuauDeferredConstraintResolution )
CHECK ( " tp1 where tp1 = { read BaseField: unknown, read BaseMethod: (tp1) -> () }, number " = = toString ( thePack ) ) ;
else
CHECK ( " tp1 where tp1 = {| BaseField: unknown, BaseMethod: (tp1) -> () |}, number " = = toString ( thePack ) ) ;
}
2024-08-10 00:46:26 +08:00
TEST_CASE_FIXTURE ( Fixture , " correct_stringification_user_defined_type_functions " )
{
TypeFunction user { " user " , nullptr } ;
TypeFunctionInstanceType tftt {
NotNull { & user } ,
std : : vector < TypeId > { builtinTypes - > numberType } , // Type Function Arguments
{ } ,
{ AstName { " woohoo " } } , // Type Function Name
std : : nullopt
} ;
Type tv { tftt } ;
if ( FFlag : : DebugLuauDeferredConstraintResolution & & FFlag : : LuauUserDefinedTypeFunctions )
CHECK_EQ ( toString ( & tv , { } ) , " woohoo<number> " ) ;
}
2021-10-30 04:25:12 +08:00
TEST_SUITE_END ( ) ;