mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
16fbfe912c
- Cleaned up `FFlag::FixFindBindingAtFunctionName`, `FFlag::LuauNormalizeBlockedTypes`, `FFlag::LuauPCallDebuggerFix` - Added support for break and continue into control flow analysis - The old type unification engine will now report a more fine-grained error at times, indicating that type normalization in particular failed # New Type Solver - Refactor of Unifier2, the new unification implementation for Luau - Completed MVP of new unification implementation - Dramatically simplified overload selection logic - Type family reduction can now apply sooner to free types that have been solved - Subtyping now supports table indexers - Generalization now replaces bad generics with unknown # Native Code Generation - Reduce stack spills caused by FINDUPVAL and STORE_TAG - Improve Generate SHL/SHR/SAR/rotates with immediate operands in X64 - Removed redundant case re-check in table lookup fallback --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com>
345 lines
8.3 KiB
C++
345 lines
8.3 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Fixture.h"
|
|
|
|
#include "doctest.h"
|
|
|
|
using namespace Luau;
|
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|
|
|
TEST_SUITE_BEGIN("TypeInferUnknownNever");
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "string_subtype_and_unknown_supertype")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: string)
|
|
local foo: unknown = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "unknown_subtype_and_string_supertype")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: unknown)
|
|
local foo: string = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "unknown_is_reflexive")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: unknown)
|
|
local foo: unknown = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "string_subtype_and_never_supertype")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: string)
|
|
local foo: never = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "never_subtype_and_string_supertype")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: never)
|
|
local foo: string = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "never_is_reflexive")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: never)
|
|
local foo: never = x
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "unknown_is_optional_because_it_too_encompasses_nil")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: {x: unknown} = {}
|
|
)");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "table_with_prop_of_type_never_is_uninhabitable")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: {x: never} = {}
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "table_with_prop_of_type_never_is_also_reflexive")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: {x: never} = {x = 5 :: never}
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "array_like_table_of_never_is_inhabitable")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: {never} = {}
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f() return "foo", 5 :: never end
|
|
|
|
local x, y, z = f()
|
|
)");
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
{
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK_EQ("Function only returns 2 values, but 3 are required here", toString(result.errors[0]));
|
|
|
|
CHECK_EQ("string", toString(requireType("x")));
|
|
CHECK_EQ("never", toString(requireType("y")));
|
|
CHECK_EQ("*error-type*", toString(requireType("z")));
|
|
}
|
|
else
|
|
{
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("never", toString(requireType("x")));
|
|
CHECK_EQ("never", toString(requireType("y")));
|
|
CHECK_EQ("never", toString(requireType("z")));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "type_packs_containing_never_is_itself_uninhabitable2")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(): (string, never) return "", 5 :: never end
|
|
local function g(): (never, string) return 5 :: never, "" end
|
|
|
|
local x1, x2 = f()
|
|
local y1, y2 = g()
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
{
|
|
CHECK_EQ("string", toString(requireType("x1")));
|
|
CHECK_EQ("never", toString(requireType("x2")));
|
|
CHECK_EQ("never", toString(requireType("y1")));
|
|
CHECK_EQ("string", toString(requireType("y2")));
|
|
}
|
|
else
|
|
{
|
|
CHECK_EQ("never", toString(requireType("x1")));
|
|
CHECK_EQ("never", toString(requireType("x2")));
|
|
CHECK_EQ("never", toString(requireType("y1")));
|
|
CHECK_EQ("never", toString(requireType("y2")));
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "index_on_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local x: never = 5 :: never
|
|
local z = x.y
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("never", toString(requireType("z")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "call_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local f: never = 5 :: never
|
|
local x, y, z = f()
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("never", toString(requireType("x")));
|
|
CHECK_EQ("never", toString(requireType("y")));
|
|
CHECK_EQ("never", toString(requireType("z")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "assign_to_local_which_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: never
|
|
t = 3
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "assign_to_global_which_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
--!nonstrict
|
|
t = 5 :: never
|
|
t = ""
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "assign_to_prop_which_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: never
|
|
t.x = 5
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local t: never
|
|
t[5] = 7
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "for_loop_over_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
for i, v in (5 :: never) do
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "pick_never_from_variadic_type_pack")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(...: never)
|
|
local x, y = (...)
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
type Disjoint = {foo: never, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
|
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
|
local foo = disjoint.foo
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("never", toString(requireType("foo")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "index_on_union_of_tables_for_properties_that_is_sorta_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
type Disjoint = {foo: string, bar: unknown, tag: "ok"} | {foo: never, baz: unknown, tag: "err"}
|
|
local disjoint: Disjoint = {foo = 5 :: never, bar = true, tag = "ok"}
|
|
local foo = disjoint.foo
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("string", toString(requireType("foo")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "unary_minus_of_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local x = -(5 :: never)
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("never", toString(requireType("x")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "length_of_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local x = #({} :: never)
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
CHECK_EQ("number", toString(requireType("x")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function ord(x: nil, y)
|
|
return x ~= nil and x > y
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
CHECK_EQ("(nil, unknown) -> boolean", toString(requireType("ord")));
|
|
else
|
|
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("ord")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function mul(x: nil, y)
|
|
return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK_EQ("<a>(nil, a) -> boolean", toString(requireType("mul")));
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "compare_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function cmp(x: nil, y: number)
|
|
return x ~= nil and x > y and x < y -- infers boolean | never, which is normalized into boolean
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
CHECK_EQ("(nil, number) -> boolean", toString(requireType("cmp")));
|
|
}
|
|
|
|
TEST_SUITE_END();
|