mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
77295c3610
Some checks failed
benchmark / callgrind (map[branch:main name:luau-lang/benchmark-data], ubuntu-22.04) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:macos-arm version:macos-14]) (push) Has been cancelled
build / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-latest]) (push) Has been cancelled
build / windows (Win32) (push) Has been cancelled
build / windows (x64) (push) Has been cancelled
build / coverage (push) Has been cancelled
build / web (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:macos version:macos-latest]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:ubuntu version:ubuntu-20.04]) (push) Has been cancelled
release / ${{matrix.os.name}} (map[name:windows version:windows-latest]) (push) Has been cancelled
release / web (push) Has been cancelled
# General Updates Fix an old solver crash that occurs in the presence of cyclic `requires()` ## New Solver - Improvements to Luau user-defined type function library - Avoid asserting on unexpected metatable types - Properties in user defined type functions should have a consistent iteration order - in this case it is insertion ordering # Runtime - Track VM allocations for telemetry --- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: James McNellis <jmcnellis@roblox.com> Co-authored-by: Varun Saini <vsaini@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Junseo Yoo <jyoo@roblox.com>
1267 lines
37 KiB
C++
1267 lines
37 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/TypeFunction.h"
|
|
|
|
#include "Luau/ConstraintSolver.h"
|
|
#include "Luau/NotNull.h"
|
|
#include "Luau/Type.h"
|
|
|
|
#include "ClassFixture.h"
|
|
#include "Fixture.h"
|
|
|
|
#include "doctest.h"
|
|
|
|
using namespace Luau;
|
|
|
|
LUAU_FASTFLAG(LuauSolverV2)
|
|
LUAU_FASTFLAG(LuauUserDefinedTypeFunctions2)
|
|
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
|
|
|
struct TypeFunctionFixture : Fixture
|
|
{
|
|
TypeFunction swapFunction;
|
|
|
|
TypeFunctionFixture()
|
|
: Fixture(false)
|
|
{
|
|
swapFunction = TypeFunction{
|
|
/* name */ "Swap",
|
|
/* reducer */
|
|
[](TypeId instance, const std::vector<TypeId>& tys, const std::vector<TypePackId>& tps, NotNull<TypeFunctionContext> ctx
|
|
) -> TypeFunctionReductionResult<TypeId>
|
|
{
|
|
LUAU_ASSERT(tys.size() == 1);
|
|
TypeId param = follow(tys.at(0));
|
|
|
|
if (isString(param))
|
|
{
|
|
return TypeFunctionReductionResult<TypeId>{ctx->builtins->numberType, false, {}, {}};
|
|
}
|
|
else if (isNumber(param))
|
|
{
|
|
return TypeFunctionReductionResult<TypeId>{ctx->builtins->stringType, false, {}, {}};
|
|
}
|
|
else if (is<BlockedType>(param) || is<PendingExpansionType>(param) || is<TypeFunctionInstanceType>(param) ||
|
|
(ctx->solver && ctx->solver->hasUnresolvedConstraints(param)))
|
|
{
|
|
return TypeFunctionReductionResult<TypeId>{std::nullopt, false, {param}, {}};
|
|
}
|
|
else
|
|
{
|
|
return TypeFunctionReductionResult<TypeId>{std::nullopt, true, {}, {}};
|
|
}
|
|
}
|
|
};
|
|
|
|
unfreeze(frontend.globals.globalTypes);
|
|
TypeId t = frontend.globals.globalTypes.addType(GenericType{"T"});
|
|
GenericTypeDefinition genericT{t};
|
|
|
|
ScopePtr globalScope = frontend.globals.globalScope;
|
|
globalScope->exportedTypeBindings["Swap"] =
|
|
TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFunctionInstanceType{NotNull{&swapFunction}, {t}, {}})};
|
|
freeze(frontend.globals.globalTypes);
|
|
}
|
|
};
|
|
|
|
TEST_SUITE_BEGIN("TypeFunctionTests");
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "basic_type_function")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type A = Swap<number>
|
|
type B = Swap<string>
|
|
type C = Swap<boolean>
|
|
|
|
local x = 123
|
|
local y: Swap<typeof(x)> = "foo"
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK("string" == toString(requireTypeAlias("A")));
|
|
CHECK("number" == toString(requireTypeAlias("B")));
|
|
CHECK("Swap<boolean>" == toString(requireTypeAlias("C")));
|
|
CHECK("string" == toString(requireType("y")));
|
|
CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
|
};
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "function_as_fn_ret")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local swapper: <T>(T) -> Swap<T>
|
|
local a = swapper(123)
|
|
local b = swapper("foo")
|
|
local c = swapper(false)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK("string" == toString(requireType("a")));
|
|
CHECK("number" == toString(requireType("b")));
|
|
CHECK("Swap<boolean>" == toString(requireType("c")));
|
|
CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "function_as_fn_arg")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local swapper: <T>(Swap<T>) -> T
|
|
local a = swapper(123)
|
|
local b = swapper(false)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK("unknown" == toString(requireType("a")));
|
|
CHECK("unknown" == toString(requireType("b")));
|
|
CHECK("Type 'number' could not be converted into 'never'" == toString(result.errors[0]));
|
|
CHECK("Type 'boolean' could not be converted into 'never'" == toString(result.errors[1]));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "resolve_deep_functions")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local x: Swap<Swap<Swap<string>>>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK("number" == toString(requireType("x")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local impossible: <T>(Swap<T>) -> Swap<Swap<T>>
|
|
local a = impossible(123)
|
|
local b = impossible(true)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(6, result);
|
|
CHECK(toString(result.errors[0]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
|
CHECK(toString(result.errors[1]) == "Type function instance Swap<T> is uninhabited");
|
|
CHECK(toString(result.errors[2]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
|
CHECK(toString(result.errors[3]) == "Type function instance Swap<T> is uninhabited");
|
|
CHECK(toString(result.errors[4]) == "Type function instance Swap<Swap<T>> is uninhabited");
|
|
CHECK(toString(result.errors[5]) == "Type function instance Swap<T> is uninhabited");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local t: <T>({T}) -> {Swap<T>}
|
|
local a = t({1, 2, 3})
|
|
local b = t({"a", "b", "c"})
|
|
local c = t({true, false, true})
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(requireType("a")) == "{string}");
|
|
CHECK(toString(requireType("b")) == "{number}");
|
|
// FIXME: table types are constructing a trivial union here.
|
|
CHECK(toString(requireType("c")) == "{Swap<boolean | boolean | boolean>}");
|
|
CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean | boolean | boolean> is uninhabited");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "function_internal_functions")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local f0: <T>(T) -> (() -> T)
|
|
local f: <T>(T) -> (() -> Swap<T>)
|
|
local a = f(1)
|
|
local b = f("a")
|
|
local c = f(true)
|
|
local d = f0(1)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(requireType("a")) == "() -> string");
|
|
CHECK(toString(requireType("b")) == "() -> number");
|
|
CHECK(toString(requireType("c")) == "() -> Swap<boolean>");
|
|
CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean> is uninhabited");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "add_function_at_work")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local function add(a, b)
|
|
return a + b
|
|
end
|
|
|
|
local a = add(1, 2)
|
|
local b = add(1, "foo")
|
|
local c = add("foo", 1)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(requireType("a")) == "number");
|
|
CHECK(toString(requireType("b")) == "add<number, string>");
|
|
CHECK(toString(requireType("c")) == "add<string, number>");
|
|
CHECK(
|
|
toString(result.errors[0]) ==
|
|
"Operator '+' could not be applied to operands of types number and string; there is no corresponding overload for __add"
|
|
);
|
|
CHECK(
|
|
toString(result.errors[1]) ==
|
|
"Operator '+' could not be applied to operands of types string and number; there is no corresponding overload for __add"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_function_at_work")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type T = add<number | T, number>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireTypeAlias("T")) == "number");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "mul_function_with_union_of_multiplicatives")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
loadDefinition(R"(
|
|
declare class Vec2
|
|
function __mul(self, rhs: number): Vec2
|
|
end
|
|
|
|
declare class Vec3
|
|
function __mul(self, rhs: number): Vec3
|
|
end
|
|
)");
|
|
|
|
CheckResult result = check(R"(
|
|
type T = mul<Vec2 | Vec3, number>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireTypeAlias("T")) == "Vec2 | Vec3");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "mul_function_with_union_of_multiplicatives_2")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
loadDefinition(R"(
|
|
declare class Vec3
|
|
function __mul(self, rhs: number): Vec3
|
|
function __mul(self, rhs: Vec3): Vec3
|
|
end
|
|
)");
|
|
|
|
CheckResult result = check(R"(
|
|
type T = mul<number | Vec3, Vec3>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireTypeAlias("T")) == "Vec3");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "internal_functions_raise_errors")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local function innerSum(a, b)
|
|
local _ = a + b
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(
|
|
toString(result.errors[0]) ==
|
|
"Operator '+' could not be applied to operands of types unknown and unknown; there is no corresponding overload for __add"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_functions_can_be_shadowed")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type add<T> = string -- shadow add
|
|
|
|
-- this should be ok
|
|
function hi(f: add<unknown>)
|
|
return string.format("hi %s", f)
|
|
end
|
|
|
|
-- this should still work totally fine (and use the real type function)
|
|
function plus(a, b)
|
|
return a + b
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK(toString(requireType("hi")) == "(string) -> string");
|
|
CHECK(toString(requireType("plus")) == "<a, b>(a, b) -> add<a, b>");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "type_functions_inhabited_with_normalization")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local useGridConfig : any
|
|
local columns = useGridConfig("columns", {}) or 1
|
|
local gutter = useGridConfig('gutter', {}) or 0
|
|
local margin = useGridConfig('margin', {}) or 0
|
|
return function(frameAbsoluteWidth: number)
|
|
local cellAbsoluteWidth = (frameAbsoluteWidth - 2 * margin + gutter) / columns - gutter
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type KeysOfMyObject = keyof<MyObject>
|
|
|
|
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works_with_metatables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local metatable = { __index = {w = 1} }
|
|
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
|
type MyObject = typeof(obj)
|
|
type KeysOfMyObject = keyof<MyObject>
|
|
|
|
local function ok(idx: KeysOfMyObject): "w" | "x" | "y" | "z" return idx end
|
|
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_single_entry_no_uniontype")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local tbl_A = { abc = "value" }
|
|
local tbl_B = { a1 = nil, ["a2"] = nil }
|
|
|
|
type keyof_A = keyof<typeof(tbl_A)>
|
|
type keyof_B = keyof<typeof(tbl_B)>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK(toString(requireTypeAlias("keyof_A")) == "\"abc\"");
|
|
CHECK(toString(requireTypeAlias("keyof_B")) == "\"a1\" | \"a2\"");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_errors_if_it_has_nontable_part")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type KeysOfMyObject = keyof<MyObject | boolean>
|
|
|
|
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
)");
|
|
|
|
// FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think?
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid");
|
|
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_string_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type MyOtherObject = { [string]: number }
|
|
type KeysOfMyOtherObject = keyof<MyOtherObject>
|
|
type KeysOfMyObjects = keyof<MyObject | MyOtherObject>
|
|
|
|
local function ok(idx: KeysOfMyOtherObject): "z" return idx end
|
|
local function err(idx: KeysOfMyObjects): "z" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("string", toString(tpm->givenTp));
|
|
|
|
tpm = get<TypePackMismatch>(result.errors[1]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_common_subset_if_union_of_differing_tables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type MyOtherObject = { w: number, y: number, z: number }
|
|
type KeysOfMyObject = keyof<MyObject | MyOtherObject>
|
|
|
|
local function err(idx: KeysOfMyObject): "z" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_never_for_empty_table")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeyofEmpty = keyof<{}>
|
|
|
|
local foo = ((nil :: any) :: KeyofEmpty)
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireType("foo")) == "never");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_works")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type KeysOfMyObject = rawkeyof<MyObject>
|
|
|
|
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_ignores_metatables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local metatable = { __index = {w = 1} }
|
|
local obj = setmetatable({x = 1, y = 2, z = 3}, metatable)
|
|
type MyObject = typeof(obj)
|
|
type KeysOfMyObject = rawkeyof<MyObject>
|
|
|
|
local function ok(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
local function err(idx: KeysOfMyObject): "x" | "y" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"x\" | \"y\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_errors_if_it_has_nontable_part")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type KeysOfMyObject = rawkeyof<MyObject | boolean>
|
|
|
|
local function err(idx: KeysOfMyObject): "x" | "y" | "z" return idx end
|
|
)");
|
|
|
|
// FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think?
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid");
|
|
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_common_subset_if_union_of_differing_tables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = { x: number, y: number, z: number }
|
|
type MyOtherObject = { w: number, y: number, z: number }
|
|
type KeysOfMyObject = rawkeyof<MyObject | MyOtherObject>
|
|
|
|
local function err(idx: KeysOfMyObject): "z" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"z\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_never_for_empty_table")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type RawkeyofEmpty = rawkeyof<{}>
|
|
|
|
local foo = ((nil :: any) :: RawkeyofEmpty)
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireType("foo")) == "never");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_on_classes")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = keyof<BaseClass>
|
|
|
|
local function ok(idx: KeysOfMyObject): "BaseMethod" | "BaseField" | "Touched" return idx end
|
|
local function err(idx: KeysOfMyObject): "BaseMethod" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("\"BaseMethod\"", toString(tpm->wantedTp));
|
|
CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_errors_if_it_has_nonclass_part")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = keyof<BaseClass | boolean>
|
|
|
|
local function err(idx: KeysOfMyObject): "BaseMethod" | "BaseField" return idx end
|
|
)");
|
|
|
|
// FIXME(CLI-95289): we should actually only report the type function being uninhabited error at its first use, I think?
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid");
|
|
CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_common_subset_if_union_of_differing_classes")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = keyof<BaseClass | Vector2>
|
|
|
|
local function ok(idx: KeysOfMyObject): never return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_with_parent_classes_too")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = keyof<ChildClass>
|
|
|
|
local function ok(idx: KeysOfMyObject): "BaseField" | "BaseMethod" | "Method" | "Touched" return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "binary_type_function_works_with_default_argument")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type result = mul<number>
|
|
|
|
local function thunk(): result return 5 * 4 end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK("() -> number" == toString(requireType("thunk")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "vector2_multiply_is_overloaded")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local v = Vector2.New(1, 2)
|
|
|
|
local v2 = v * 1.5
|
|
local v3 = v * v
|
|
local v4 = v * "Hello" -- line 5
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
CHECK(5 == result.errors[0].location.begin.line);
|
|
CHECK(5 == result.errors[0].location.end.line);
|
|
|
|
CHECK("Vector2" == toString(requireType("v2")));
|
|
CHECK("Vector2" == toString(requireType("v3")));
|
|
CHECK("mul<Vector2, string>" == toString(requireType("v4")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_rfc_example")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local animals = {
|
|
cat = { speak = function() print "meow" end },
|
|
dog = { speak = function() print "woof woof" end },
|
|
monkey = { speak = function() print "oo oo" end },
|
|
fox = { speak = function() print "gekk gekk" end }
|
|
}
|
|
|
|
type AnimalType = keyof<typeof(animals)>
|
|
|
|
function speakByType(animal: AnimalType)
|
|
animals[animal].speak()
|
|
end
|
|
|
|
speakByType("dog") -- ok
|
|
speakByType("cactus") -- errors
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
|
REQUIRE(tm);
|
|
CHECK_EQ("\"cat\" | \"dog\" | \"fox\" | \"monkey\"", toString(tm->wantedType));
|
|
CHECK_EQ("\"cactus\"", toString(tm->givenType));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local EnumVariants = {
|
|
["a"] = 1, ["b"] = 2, ["c"] = 3
|
|
}
|
|
|
|
type EnumKey = keyof<typeof(EnumVariants)>
|
|
|
|
function fnA<T>(i: T): keyof<T> end
|
|
|
|
function fnB(i: EnumKey) end
|
|
|
|
local result = fnA(EnumVariants)
|
|
fnB(result)
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
|
|
CHECK(get<FunctionExitsWithoutReturning>(result.errors[1]));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType")
|
|
{
|
|
CheckResult result = check(R"(
|
|
Module 'l0':
|
|
local _ = (67108864)(_ >= _).insert
|
|
do end
|
|
do end
|
|
_(...,_(_,_(_()),_()))
|
|
(67108864)()()
|
|
_(_ ~= _ // _,l0)(_(_({n0,})),_(_),_)
|
|
_(setmetatable(_,{[...]=_,}))
|
|
|
|
)");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_concat_function_at_work")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type T = concat<string | T, string>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireTypeAlias("T")) == "string");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "exceeded_distributivity_limits")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
ScopedFastInt sfi{DFInt::LuauTypeFamilyApplicationCartesianProductLimit, 10};
|
|
|
|
loadDefinition(R"(
|
|
declare class A
|
|
function __mul(self, rhs: unknown): A
|
|
end
|
|
|
|
declare class B
|
|
function __mul(self, rhs: unknown): B
|
|
end
|
|
|
|
declare class C
|
|
function __mul(self, rhs: unknown): C
|
|
end
|
|
|
|
declare class D
|
|
function __mul(self, rhs: unknown): D
|
|
end
|
|
)");
|
|
|
|
CheckResult result = check(R"(
|
|
type T = mul<A | B | C | D, A | B | C | D>
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(get<UninhabitedTypeFunction>(result.errors[0]));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "didnt_quite_exceed_distributivity_limits")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
// We duplicate the test here because we want to make sure the test failed
|
|
// due to exceeding the limits specifically, rather than any possible reasons.
|
|
ScopedFastInt sfi{DFInt::LuauTypeFamilyApplicationCartesianProductLimit, 20};
|
|
|
|
loadDefinition(R"(
|
|
declare class A
|
|
function __mul(self, rhs: unknown): A
|
|
end
|
|
|
|
declare class B
|
|
function __mul(self, rhs: unknown): B
|
|
end
|
|
|
|
declare class C
|
|
function __mul(self, rhs: unknown): C
|
|
end
|
|
|
|
declare class D
|
|
function __mul(self, rhs: unknown): D
|
|
end
|
|
)");
|
|
|
|
CheckResult result = check(R"(
|
|
type T = mul<A | B | C | D, A | B | C | D>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_equivalence_with_distributivity")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
loadDefinition(R"(
|
|
declare class A
|
|
function __mul(self, rhs: unknown): A
|
|
end
|
|
|
|
declare class B
|
|
function __mul(self, rhs: unknown): B
|
|
end
|
|
|
|
declare class C
|
|
function __mul(self, rhs: unknown): C
|
|
end
|
|
|
|
declare class D
|
|
function __mul(self, rhs: unknown): D
|
|
end
|
|
)");
|
|
|
|
CheckResult result = check(R"(
|
|
type T = mul<A | B, C | D>
|
|
type U = mul<A, C> | mul<A, D> | mul<B, C> | mul<B, D>
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK(toString(requireTypeAlias("T")) == "A | B");
|
|
CHECK(toString(requireTypeAlias("U")) == "A | A | B | B");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "we_shouldnt_warn_that_a_reducible_type_function_is_uninhabited")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
|
|
local Debounce = false
|
|
local Active = false
|
|
|
|
local function Use(Mode)
|
|
|
|
if Mode ~= nil then
|
|
|
|
if Mode == false and Active == false then
|
|
return
|
|
else
|
|
Active = not Mode
|
|
end
|
|
|
|
Debounce = false
|
|
end
|
|
Active = not Active
|
|
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type IdxAType = index<MyObject, "a">
|
|
type IdxBType = index<MyObject, keyof<MyObject>>
|
|
|
|
local function ok(idx: IdxAType): string return idx end
|
|
local function ok2(idx: IdxBType): string | number | boolean return idx end
|
|
local function err(idx: IdxAType): boolean return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("boolean", toString(tpm->wantedTp));
|
|
CHECK_EQ("string", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local PlayerData = {
|
|
Coins = 0,
|
|
Level = 1,
|
|
Exp = 0,
|
|
MaxExp = 100
|
|
}
|
|
type Keys = index<typeof(PlayerData), keyof<typeof(PlayerData)>>
|
|
-- This function makes it think that there's going to be a pending expansion
|
|
local function UpdateData(key: Keys, value)
|
|
PlayerData[key] = value
|
|
end
|
|
UpdateData("Coins", 2)
|
|
)");
|
|
|
|
// Should not crash!
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local MyObject = {"hello", 1, true}
|
|
type IdxAType = index<typeof(MyObject), number>
|
|
|
|
local function ok(idx: IdxAType): string | number | boolean return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_generic_types")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local function access<T, K>(tbl: T & {}, key: K): index<T, K>
|
|
return tbl[key]
|
|
end
|
|
|
|
local subjects = {
|
|
english = "boring",
|
|
math = "fun"
|
|
}
|
|
|
|
local key: "english" = "english"
|
|
local a: string = access(subjects, key)
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_bad_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type errType1 = index<MyObject, "d">
|
|
type errType2 = index<MyObject, boolean>
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"d\"' does not exist on type 'MyObject'");
|
|
CHECK(toString(result.errors[1]) == "Property 'boolean' does not exist on type 'MyObject'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
local key = "a"
|
|
|
|
type errType1 = index<MyObject, key>
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Second argument to index<MyObject, _> is not a valid index type");
|
|
CHECK(toString(result.errors[1]) == "Unknown type 'key'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_union_type_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
|
|
type idxType = index<MyObject, "a" | "b">
|
|
local function ok(idx: idxType): string | number return idx end
|
|
|
|
type errType = index<MyObject, "a" | "d">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_union_type_indexee")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type MyObject2 = {a: number}
|
|
|
|
type idxTypeA = index<MyObject | MyObject2, "a">
|
|
local function ok(idx: idxTypeA): string | number return idx end
|
|
|
|
type errType = index<MyObject | MyObject2, "b">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_rfc_alternative_section")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string}
|
|
type MyObject2 = {a: string, b: number}
|
|
|
|
local function edgeCase(param: MyObject)
|
|
type unknownType = index<typeof(param), "b">
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = index<BaseClass, "BaseField">
|
|
|
|
local function ok(idx: KeysOfMyObject): number return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes_with_parents")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type KeysOfMyObject = index<ChildClass, "BaseField">
|
|
|
|
local function ok(idx: KeysOfMyObject): number return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_index_metatables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local exampleClass = { Foo = "text", Bar = true }
|
|
|
|
local exampleClass2 = setmetatable({ Foo = 8 }, { __index = exampleClass })
|
|
type exampleTy2 = index<typeof(exampleClass2), "Foo">
|
|
local function ok(idx: exampleTy2): number return idx end
|
|
|
|
local exampleClass3 = setmetatable({ Bar = 5 }, { __index = exampleClass })
|
|
type exampleTy3 = index<typeof(exampleClass3), "Foo">
|
|
local function ok2(idx: exampleTy3): string return idx end
|
|
|
|
type exampleTy4 = index<typeof(exampleClass3), "Foo" | "Bar">
|
|
local function ok3(idx: exampleTy4): string | number return idx end
|
|
|
|
type errTy = index<typeof(exampleClass2), "Car">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"Car\"' does not exist on type 'exampleClass2'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type RawAType = rawget<MyObject, "a">
|
|
type RawBType = rawget<MyObject, keyof<MyObject>>
|
|
local function ok(idx: RawAType): string return idx end
|
|
local function ok2(idx: RawBType): string | number | boolean return idx end
|
|
local function err(idx: RawAType): boolean return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
TypePackMismatch* tpm = get<TypePackMismatch>(result.errors[0]);
|
|
REQUIRE(tpm);
|
|
CHECK_EQ("boolean", toString(tpm->wantedTp));
|
|
CHECK_EQ("string", toString(tpm->givenTp));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_array")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local MyObject = {"hello", 1, true}
|
|
type RawAType = rawget<typeof(MyObject), number>
|
|
local function ok(idx: RawAType): string | number | boolean return idx end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_errors_w_var_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
local key = "a"
|
|
type errType1 = rawget<MyObject, key>
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Second argument to rawget<MyObject, _> is not a valid index type");
|
|
CHECK(toString(result.errors[1]) == "Unknown type 'key'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexer")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type rawType = rawget<MyObject, "a" | "b">
|
|
local function ok(idx: rawType): string | number return idx end
|
|
type errType = rawget<MyObject, "a" | "d">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexee")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type MyObject = {a: string, b: number, c: boolean}
|
|
type MyObject2 = {a: number}
|
|
type rawTypeA = rawget<MyObject | MyObject2, "a">
|
|
local function ok(idx: rawTypeA): string | number return idx end
|
|
type errType = rawget<MyObject | MyObject2, "b">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_index_metatables")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local exampleClass = { Foo = "text", Bar = true }
|
|
local exampleClass2 = setmetatable({ Foo = 8 }, { __index = exampleClass })
|
|
type exampleTy2 = rawget<typeof(exampleClass2), "Foo">
|
|
local function ok(idx: exampleTy2): number return idx end
|
|
local exampleClass3 = setmetatable({ Bar = 5 }, { __index = exampleClass })
|
|
type errType = rawget<typeof(exampleClass3), "Foo">
|
|
type errType2 = rawget<typeof(exampleClass3), "Bar" | "Foo">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"Foo\"' does not exist on type 'exampleClass3'");
|
|
CHECK(toString(result.errors[1]) == "Property '\"Bar\" | \"Foo\"' does not exist on type 'exampleClass3'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes")
|
|
{
|
|
if (!FFlag::LuauSolverV2)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
type PropsOfMyObject = rawget<BaseClass, "BaseField">
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow")
|
|
{
|
|
// Should not fail assertions
|
|
check(R"(
|
|
local _
|
|
_ = true
|
|
for l0=_,_,# _ do
|
|
end
|
|
for l0=_,_ do
|
|
if _ then
|
|
_ += _
|
|
end
|
|
end
|
|
)");
|
|
}
|
|
|
|
TEST_SUITE_END();
|