luau/tests/TypeInfer.unknownnever.test.cpp
Alexander McCord c2ba1058c3
Sync to upstream/release/603 (#1097)
# What's changed?

- Record the location of properties for table types (closes #802)
- Implement stricter UTF-8 validations as per the RFC
(https://github.com/luau-lang/rfcs/pull/1)
- Implement `buffer` as a new type in both the old and new solvers.
- Changed errors produced by some `buffer` builtins to be a bit more
generic to avoid platform-dependent error messages.
- Fixed a bug where `Unifier` would copy some persistent types, tripping
some internal assertions.
- Type checking rules on relational operators is now a little bit more
lax.
- Improve dead code elimination for some `if` statements with complex
always-false conditions

## New type solver

- Dataflow analysis now generates phi nodes on exit of branches.
- Dataflow analysis avoids producing a new definition for locals or
properties that are not owned by that loop.
- If a function parameter has been constrained to `never`, report errors
at all uses of that parameter within that function.
- Switch to using the new `Luau::Set` to replace `std::unordered_set` to
alleviate some poor allocation characteristics which was negatively
affecting overall performance.
- Subtyping can now report many failing reasons instead of just the
first one that we happened to find during the test.
- Subtyping now also report reasons for type pack mismatches.
- When visiting `if` statements or expressions, the resulting context
are the common terms in both branches.

## Native codegen

- Implement support for `buffer` builtins to its IR for x64 and A64.
- Optimized `table.insert` by not inserting a table barrier if it is
fastcalled with a constant.

## 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: Arseny Kapoulkine <arseny@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-11-10 13:10:07 -08:00

390 lines
9.7 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_CASE_FIXTURE(Fixture, "lti_error_at_declaration_for_never_normalizations")
{
ScopedFastFlag sff_DebugLuauDeferredConstraintResolution{"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{"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();