mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
25f91aa8b8
# What's Changed? - Variety of bugfixes in the new solver ## New Solver - Fix an issue where we would hit a recursion limit when applying long chains of type refinements. - Weaken the types of `table.freeze` and `table.clone` in the new solver so we can accept common code patterns like `local a = table.freeze({x=5, x=0})` at the expense of accepting code like `table.freeze(true)`. - Don't warn when the # operator is used on a value of type never ## VM - Fix a bug in lua_resume where too many values might be removed from stack when resume throws an error --- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@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>
423 lines
10 KiB
C++
423 lines
10 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("Function only returns 2 values, but 3 are required here" == toString(result.errors[0]));
|
|
|
|
CHECK("string" == toString(requireType("x")));
|
|
CHECK("never" == toString(requireType("y")));
|
|
CHECK("nil" == 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 function f(t: never)
|
|
t.x = 5
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "assign_to_subscript_which_is_never")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(t: never)
|
|
t[5] = 7
|
|
end
|
|
)");
|
|
|
|
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"}
|
|
|
|
function f(disjoint: Disjoint)
|
|
return disjoint.foo
|
|
end
|
|
|
|
local foo = f({foo = 5 :: never, bar = true, tag = "ok"})
|
|
)");
|
|
|
|
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"}
|
|
|
|
function f(disjoint: Disjoint)
|
|
return disjoint.foo
|
|
end
|
|
|
|
local foo = f({foo = 5 :: never, bar = true, tag = "ok"})
|
|
)");
|
|
|
|
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
|
|
)");
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
{
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[0]));
|
|
|
|
// CLI-114134 Egraph-based simplification.
|
|
// CLI-116549 x ~= nil : false when x : nil
|
|
CHECK("<a>(nil, a) -> and<boolean, mul<nil & ~nil, a>>" == toString(requireType("mul")));
|
|
}
|
|
else
|
|
{
|
|
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_CASE_FIXTURE(Fixture, "lti_error_at_declaration_for_never_normalizations")
|
|
{
|
|
ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
local function num(x: number) end
|
|
local function str(x: string) end
|
|
local function cond(): boolean return false end
|
|
|
|
local function f(a)
|
|
if cond() then
|
|
num(a)
|
|
else
|
|
str(a)
|
|
end
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_ERROR_COUNT(3, result);
|
|
CHECK(toString(result.errors[0]) == "Parameter 'a' has been reduced to never. This function is not callable with any possible value.");
|
|
CHECK(toString(result.errors[1]) == "Parameter 'a' is required to be a subtype of 'number' here.");
|
|
CHECK(toString(result.errors[2]) == "Parameter 'a' is required to be a subtype of 'string' here.");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "lti_permit_explicit_never_annotation")
|
|
{
|
|
ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
local function num(x: number) end
|
|
local function str(x: string) end
|
|
local function cond(): boolean return false end
|
|
|
|
local function f(a: never)
|
|
if cond() then
|
|
num(a)
|
|
else
|
|
str(a)
|
|
end
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "cast_from_never_does_not_error")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function f(x: never): number
|
|
return x :: number
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_SUITE_END();
|