mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
0d2688844a
# What's Changed? - Fix #1137 by appropriately retaining additional metadata from definition files throughout the type system. - Improve Frontend for LSPs by appropriately allowing the cancellation of typechecking while running its destructor. ## New Solver - Added support for the `rawget` type function. - Reduced overall static memory usage of builtin type functions. - Fixed a crash where visitors could mutate a union or intersection type and fail to invalidate iteration over them in doing so. - Revised autocomplete functionality to not rely on a separate run of the type solver when using the new solver. - Implemented a more relaxed semantic rule for casting. - Fixed some smaller crashes in the new solver. ## Native Code Generation - Add additional codegen specialization for `math.sign` - Cleaned up a large number of outstanding fflags in the code. ### Internal Contributors 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: James McNellis <jmcnellis@roblox.com> Co-authored-by: Jeremy Yoo <jyoo@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@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>
411 lines
10 KiB
C++
411 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_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 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
|
|
)");
|
|
|
|
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();
|