luau/tests/TypeInfer.unknownnever.test.cpp
aaron ff502f0943
Sync to upstream/release/607 (#1131)
# What's changed?

* Fix up the `std::iterator_traits` definitions for some Luau data
structures.
* Replace some of the usages of `std::unordered_set` and
`std::unordered_map` with Luau-provided data structures to increase
performance and reduce overall number of heap allocations.
* Update some of the documentation links in comments throughout the
codebase to correctly point to the moved repository.
* Expanded JSON encoder for AST to support singleton types.
* Fixed a bug in `luau-analyze` where exceptions in the last module
being checked during multithreaded analysis would not be rethrown.

### New type solver

* Introduce a `refine` type family to handle deferred refinements during
type inference, replacing the old `RefineConstraint`.
* Continued work on the implementation of type states, fixing some known
bugs/blockers.
* Added support for variadic functions in new non-strict mode, enabling
broader support for builtins and the Roblox API.

### 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: 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>
2023-12-15 13:29:06 -08:00

399 lines
9.9 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_SUITE_END();