mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
206 lines
6.4 KiB
C++
206 lines
6.4 KiB
C++
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||
|
#include "Luau/TypeFamily.h"
|
||
|
#include "Luau/Type.h"
|
||
|
|
||
|
#include "Fixture.h"
|
||
|
|
||
|
#include "doctest.h"
|
||
|
|
||
|
using namespace Luau;
|
||
|
|
||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||
|
|
||
|
struct FamilyFixture : Fixture
|
||
|
{
|
||
|
TypeFamily swapFamily;
|
||
|
|
||
|
FamilyFixture()
|
||
|
: Fixture(true, false)
|
||
|
{
|
||
|
swapFamily = TypeFamily{/* name */ "Swap",
|
||
|
/* reducer */
|
||
|
[](std::vector<TypeId> tys, std::vector<TypePackId> tps, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||
|
NotNull<const TxnLog> log) -> TypeFamilyReductionResult<TypeId> {
|
||
|
LUAU_ASSERT(tys.size() == 1);
|
||
|
TypeId param = log->follow(tys.at(0));
|
||
|
|
||
|
if (isString(param))
|
||
|
{
|
||
|
return TypeFamilyReductionResult<TypeId>{builtins->numberType, false, {}, {}};
|
||
|
}
|
||
|
else if (isNumber(param))
|
||
|
{
|
||
|
return TypeFamilyReductionResult<TypeId>{builtins->stringType, false, {}, {}};
|
||
|
}
|
||
|
else if (log->get<BlockedType>(param) || log->get<FreeType>(param) || log->get<PendingExpansionType>(param) ||
|
||
|
log->get<TypeFamilyInstanceType>(param))
|
||
|
{
|
||
|
return TypeFamilyReductionResult<TypeId>{std::nullopt, false, {param}, {}};
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return TypeFamilyReductionResult<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(TypeFamilyInstanceType{NotNull{&swapFamily}, {t}, {}})};
|
||
|
freeze(frontend.globals.globalTypes);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
TEST_SUITE_BEGIN("TypeFamilyTests");
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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 family instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
||
|
};
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "type_reduction_reduces_families")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
return;
|
||
|
|
||
|
CheckResult result = check(R"(
|
||
|
local x: Swap<string> & nil
|
||
|
)");
|
||
|
|
||
|
CHECK("never" == toString(requireType("x")));
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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 family instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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);
|
||
|
// FIXME: Can we constrain these to `never` or `unknown`?
|
||
|
CHECK("a" == toString(requireType("a")));
|
||
|
CHECK("a" == toString(requireType("b")));
|
||
|
CHECK("Type family instance Swap<a> is uninhabited" == toString(result.errors[0]));
|
||
|
CHECK("Type family instance Swap<a> is uninhabited" == toString(result.errors[1]));
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "resolve_deep_families")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
return;
|
||
|
|
||
|
CheckResult result = check(R"(
|
||
|
local x: Swap<Swap<Swap<string>>>
|
||
|
)");
|
||
|
|
||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||
|
CHECK("number" == toString(requireType("x")));
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "unsolvable_family")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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(4, result);
|
||
|
for (size_t i = 0; i < 4; ++i)
|
||
|
{
|
||
|
CHECK(toString(result.errors[i]) == "Type family instance Swap<a> is uninhabited");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "table_internal_families")
|
||
|
{
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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}");
|
||
|
CHECK(toString(requireType("c")) == "{Swap<boolean>}");
|
||
|
CHECK(toString(result.errors[0]) == "Type family instance Swap<boolean> is uninhabited");
|
||
|
}
|
||
|
|
||
|
TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families")
|
||
|
{
|
||
|
// This test is broken right now, but it's not because of type families. See
|
||
|
// CLI-71143.
|
||
|
|
||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||
|
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 family instance Swap<boolean> is uninhabited");
|
||
|
}
|
||
|
|
||
|
TEST_SUITE_END();
|