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);
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
// A variable without an annotation and with a nil literal should infer as 'free', not 'nil'
|
|
|
|
CHECK_NE("nil", toString(requireType("a")));
|
|
|
|
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
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};
|
|
|
|
|
|
|
|
CHECK_EQ("t1 where t1 = { self: t1 }", toString(&cyclicTable));
|
|
|
|
}
|
|
|
|
|
|
|
|
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: {}
|
|
|
|
)");
|
|
|
|
|
|
|
|
CHECK_EQ("{| |}", toString(requireType("a")));
|
|
|
|
|
|
|
|
// Should stay the same with useLineBreaks enabled
|
|
|
|
ToStringOptions opts;
|
|
|
|
opts.useLineBreaks = true;
|
|
|
|
CHECK_EQ("{| |}", toString(requireType("a"), opts));
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
//clang-format off
|
|
|
|
CHECK_EQ("{|\n"
|
|
|
|
" anotherProp: number,\n"
|
|
|
|
" prop: string,\n"
|
|
|
|
" thirdProp: boolean\n"
|
|
|
|
"|}",
|
|
|
|
toString(requireType("a"), opts));
|
|
|
|
//clang-format on
|
|
|
|
}
|
|
|
|
|
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})};
|
2022-07-08 09:05:31 +08:00
|
|
|
CHECK_EQ("{ @metatable { }, { } }", toString(&mtv));
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
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"));
|
|
|
|
|
|
|
|
//clang-format off
|
|
|
|
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);
|
|
|
|
//clang-format on
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "intersection_parenthesized_only_if_needed")
|
|
|
|
{
|
2023-01-04 01:33:19 +08:00
|
|
|
auto utv = Type{UnionType{{typeChecker.numberType, typeChecker.stringType}}};
|
|
|
|
auto itv = Type{IntersectionType{{&utv, typeChecker.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-01-04 01:33:19 +08:00
|
|
|
auto itv = Type{IntersectionType{{typeChecker.numberType, typeChecker.stringType}}};
|
|
|
|
auto utv = Type{UnionType{{&itv, typeChecker.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")
|
|
|
|
{
|
|
|
|
auto stringAndNumberPack = TypePackVar{TypePack{{typeChecker.stringType, typeChecker.numberType}}};
|
|
|
|
auto numberAndStringPack = TypePackVar{TypePack{{typeChecker.numberType, typeChecker.stringType}}};
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
auto sn2ns = Type{FunctionType{&stringAndNumberPack, &numberAndStringPack}};
|
|
|
|
auto ns2sn = Type{FunctionType(typeChecker.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))");
|
|
|
|
}
|
|
|
|
|
2022-05-20 07:46:52 +08:00
|
|
|
TEST_CASE_FIXTURE(Fixture, "intersections_respects_use_line_breaks")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
local a: ((string) -> string) & ((number) -> number)
|
|
|
|
)");
|
|
|
|
|
|
|
|
ToStringOptions opts;
|
|
|
|
opts.useLineBreaks = true;
|
|
|
|
|
|
|
|
//clang-format off
|
|
|
|
CHECK_EQ("((number) -> number)\n"
|
|
|
|
"& ((string) -> string)",
|
|
|
|
toString(requireType("a"), opts));
|
|
|
|
//clang-format on
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "unions_respects_use_line_breaks")
|
|
|
|
{
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
local a: string | number | boolean
|
|
|
|
)");
|
|
|
|
|
|
|
|
ToStringOptions opts;
|
|
|
|
opts.useLineBreaks = true;
|
|
|
|
|
|
|
|
//clang-format off
|
|
|
|
CHECK_EQ("boolean\n"
|
|
|
|
"| number\n"
|
|
|
|
"| string",
|
|
|
|
toString(requireType("a"), opts));
|
|
|
|
//clang-format on
|
|
|
|
}
|
|
|
|
|
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"))
|
|
|
|
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
|
|
|
|
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;
|
|
|
|
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 10 more ... }");
|
|
|
|
}
|
|
|
|
|
|
|
|
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"))
|
|
|
|
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
|
|
|
|
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;
|
|
|
|
CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 2 more ... }");
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
|
|
|
|
ToStringOptions o;
|
|
|
|
o.exhaustive = false;
|
2022-11-11 06:04:44 +08:00
|
|
|
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
{
|
|
|
|
o.maxTypeLength = 30;
|
|
|
|
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
2023-02-03 20:34:12 +08:00
|
|
|
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
|
|
|
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
|
|
|
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
2022-11-11 06:04:44 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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
|
|
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
|
|
|
|
ToStringOptions o;
|
|
|
|
o.exhaustive = true;
|
2022-11-11 06:04:44 +08:00
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
|
|
{
|
|
|
|
o.maxTypeLength = 30;
|
|
|
|
CHECK_EQ(toString(requireType("f0"), o), "() -> ()");
|
2023-02-03 20:34:12 +08:00
|
|
|
CHECK_EQ(toString(requireType("f1"), o), "<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
|
|
|
CHECK_EQ(toString(requireType("f2"), o), "<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
|
|
|
CHECK_EQ(toString(requireType("f3"), o), "<c>(c) -> (<b>(b) -> (<a>(a) -> (() -> ()) | (a & ~false & ~nil)... *TRUNCATED*");
|
2022-11-11 06:04:44 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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"))
|
|
|
|
ttv.props[std::string(1, c)] = {typeChecker.numberType};
|
|
|
|
|
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;
|
2021-10-30 04:25:12 +08:00
|
|
|
CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |}");
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_union_type_bails_early")
|
|
|
|
{
|
2023-01-04 01:33:19 +08:00
|
|
|
Type tv{UnionType{{typeChecker.stringType, typeChecker.numberType}}};
|
|
|
|
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{}};
|
2021-10-30 04:25:12 +08:00
|
|
|
ttv.indexer = TableIndexer{typeChecker.numberType, typeChecker.stringType};
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
CHECK_EQ("{string}", toString(Type{ttv}));
|
2021-10-30 04:25:12 +08:00
|
|
|
|
|
|
|
ttv.props["A"] = {typeChecker.numberType};
|
2023-01-04 01:33:19 +08:00
|
|
|
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")));
|
|
|
|
|
|
|
|
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")));
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-05-14 03:16:50 +08:00
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "toStringDetailed2")
|
2021-10-30 04:25:12 +08:00
|
|
|
{
|
2022-08-26 04:55:08 +08:00
|
|
|
ScopedFastFlag sff[] = {
|
|
|
|
{"DebugLuauSharedSelf", true},
|
|
|
|
};
|
2022-07-01 07:29:02 +08:00
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
CheckResult result = check(R"(
|
|
|
|
local base = {}
|
|
|
|
function base:one() return 1 end
|
|
|
|
|
|
|
|
local child = {}
|
|
|
|
setmetatable(child, {__index=base})
|
|
|
|
function child:two() return 2 end
|
|
|
|
|
|
|
|
local inst = {}
|
|
|
|
setmetatable(inst, {__index=child})
|
|
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
|
2022-08-26 04:55:08 +08:00
|
|
|
ToStringOptions opts;
|
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
TypeId tType = requireType("inst");
|
2022-08-26 04:55:08 +08:00
|
|
|
ToStringResult r = toStringDetailed(tType, opts);
|
2022-07-01 07:29:02 +08:00
|
|
|
CHECK_EQ("{ @metatable { __index: { @metatable {| __index: base |}, child } }, inst }", r.name);
|
2023-01-04 01:33:19 +08:00
|
|
|
CHECK(0 == opts.nameMap.types.size());
|
2021-10-30 04:25:12 +08:00
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
const MetatableType* tMeta = get<MetatableType>(tType);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta);
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
TableType* tMeta2 = getMutable<TableType>(tMeta->metatable);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta2);
|
|
|
|
REQUIRE(tMeta2->props.count("__index"));
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
const MetatableType* tMeta3 = get<MetatableType>(tMeta2->props["__index"].type);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta3);
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
TableType* tMeta4 = getMutable<TableType>(tMeta3->metatable);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta4);
|
|
|
|
REQUIRE(tMeta4->props.count("__index"));
|
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
TableType* tMeta5 = getMutable<TableType>(tMeta4->props["__index"].type);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta5);
|
2022-10-28 06:22:49 +08:00
|
|
|
REQUIRE(tMeta5->props.count("one") > 0);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
2023-01-04 01:33:19 +08:00
|
|
|
TableType* tMeta6 = getMutable<TableType>(tMeta3->table);
|
2021-10-30 04:25:12 +08:00
|
|
|
REQUIRE(tMeta6);
|
2022-10-28 06:22:49 +08:00
|
|
|
REQUIRE(tMeta6->props.count("two") > 0);
|
2021-10-30 04:25:12 +08:00
|
|
|
|
|
|
|
ToStringResult oneResult = toStringDetailed(tMeta5->props["one"].type, opts);
|
|
|
|
|
|
|
|
std::string twoResult = toString(tMeta6->props["two"].type, opts);
|
|
|
|
|
2022-07-01 07:29:02 +08:00
|
|
|
CHECK_EQ("<a>(a) -> number", oneResult.name);
|
|
|
|
CHECK_EQ("<b>(b) -> number", twoResult);
|
2021-10-30 04:25:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "toStringErrorPack")
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
ttv->props["hello"] = {typeChecker.numberType};
|
|
|
|
ttv->props["world"] = {typeChecker.numberType};
|
|
|
|
|
|
|
|
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;
|
|
|
|
bttv->props["hello"] = {typeChecker.numberType};
|
|
|
|
bttv->boundTo = &tv1;
|
|
|
|
|
|
|
|
TypePackVar tpv2{TypePack{{&tv2}}};
|
|
|
|
|
|
|
|
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv1));
|
|
|
|
CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2));
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a")));
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
CHECK_EQ("map<a, b>(arr: {a}, fn: (a) -> b): {b}", toStringNamedFunction("map", *ftv));
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
)");
|
|
|
|
|
|
|
|
CHECK("<a, b>(a, b) -> ()" == toString(requireType("foo")));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
|
|
|
|
{
|
2022-07-01 07:29:02 +08:00
|
|
|
ScopedFastFlag sff[]{
|
|
|
|
{"DebugLuauSharedSelf", true},
|
|
|
|
};
|
|
|
|
|
2022-05-20 07:46:52 +08:00
|
|
|
CheckResult result = check(R"(
|
|
|
|
local foo = {}
|
|
|
|
function foo:method(arg: string): ()
|
|
|
|
end
|
|
|
|
)");
|
|
|
|
|
|
|
|
TypeId parentTy = requireType("foo");
|
2023-01-04 01:33:19 +08:00
|
|
|
auto ttv = get<TableType>(follow(parentTy));
|
|
|
|
auto ftv = get<FunctionType>(ttv->props.at("method").type);
|
2022-05-20 07:46:52 +08:00
|
|
|
|
|
|
|
CHECK_EQ("foo:method<a>(self: a, arg: string): ()", toStringNamedFunction("foo:method", *ftv));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_hide_self_param")
|
|
|
|
{
|
2022-07-01 07:29:02 +08:00
|
|
|
ScopedFastFlag sff[]{
|
|
|
|
{"DebugLuauSharedSelf", true},
|
|
|
|
};
|
|
|
|
|
2022-05-20 07:46:52 +08:00
|
|
|
CheckResult result = check(R"(
|
|
|
|
local foo = {}
|
|
|
|
function foo:method(arg: string): ()
|
|
|
|
end
|
|
|
|
)");
|
|
|
|
|
|
|
|
TypeId parentTy = requireType("foo");
|
2023-01-04 01:33:19 +08:00
|
|
|
auto ttv = get<TableType>(follow(parentTy));
|
|
|
|
auto ftv = get<FunctionType>(ttv->props.at("method").type);
|
2022-05-20 07:46:52 +08:00
|
|
|
|
|
|
|
ToStringOptions opts;
|
|
|
|
opts.hideFunctionSelfArgument = true;
|
|
|
|
CHECK_EQ("foo:method<a>(arg: string): ()", toStringNamedFunction("foo:method", *ftv, opts));
|
|
|
|
}
|
|
|
|
|
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}) -> ()");
|
|
|
|
}
|
|
|
|
|
2021-10-30 04:25:12 +08:00
|
|
|
TEST_SUITE_END();
|