luau/tests/NonstrictMode.test.cpp
Andy Friesen d2ab5df62b
Sync to upstream/release/565 (#845)
We've made a few small changes to reduce the amount of stack we use when
typechecking nested method calls (eg `foo:bar():baz():quux()`).

We've also fixed a small bytecode compiler issue that caused us to emit
redundant jump instructions in code that conditionally uses `break` or
`continue`.

On the new solver, we've switched to a new, better way to handle
augmentations to unsealed tables. We've also made some substantial
improvements to type inference and error reporting on function calls.
These things should both be on par with the old solver now.

The main improvements to the native code generator have been elimination
of some redundant type tag checks. Also, we are starting to inline
particular fastcalls directly to IR.

---------

Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-02-24 13:49:38 -08:00

310 lines
7.1 KiB
C++

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Scope.h"
#include "Luau/TypeInfer.h"
#include "Luau/Type.h"
#include "Fixture.h"
#include "doctest.h"
#include <algorithm>
using namespace Luau;
TEST_SUITE_BEGIN("NonstrictModeTests");
TEST_CASE_FIXTURE(Fixture, "infer_nullary_function")
{
CheckResult result = check(R"(
--!nonstrict
function foo(x, y) end
)");
TypeId fooType = requireType("foo");
REQUIRE(fooType);
const FunctionType* ftv = get<FunctionType>(fooType);
REQUIRE_MESSAGE(ftv != nullptr, "Expected a function, got " << toString(fooType));
auto args = flatten(ftv->argTypes).first;
REQUIRE_EQ(2, args.size());
REQUIRE_EQ("any", toString(args[0]));
REQUIRE_EQ("any", toString(args[1]));
auto rets = flatten(ftv->retTypes).first;
REQUIRE_EQ(0, rets.size());
}
TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_could_return")
{
CheckResult result = check(R"(
--!nonstrict
function getMinCardCountForWidth(width)
if width < 513 then
return 3
else
return 8, 'jellybeans'
end
end
)");
TypeId t = requireType("getMinCardCountForWidth");
REQUIRE(t);
REQUIRE_EQ("(any) -> (...any)", toString(t));
}
#if 0
// Maybe we want this?
TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked")
{
CheckResult result = check(R"(
function foo(x): number return 'hello' end
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE_NE(*typeChecker.anyType, *requireType("foo"));
}
#endif
TEST_CASE_FIXTURE(Fixture, "function_parameters_are_any")
{
CheckResult result = check(R"(
--!nonstrict
function f(arg)
arg = 9
arg:concat(4)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "inconsistent_return_types_are_ok")
{
CheckResult result = check(R"(
--!nonstrict
function f()
if 1 then
return 4
else
return 'hello'
end
return 'one', 'two'
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "locals_are_any_by_default")
{
CheckResult result = check(R"(
--!nonstrict
local m = 55
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*typeChecker.anyType, *requireType("m"));
}
TEST_CASE_FIXTURE(Fixture, "parameters_having_type_any_are_optional")
{
CheckResult result = check(R"(
--!nonstrict
local function f(a, b)
return a
end
f(5)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "local_tables_are_not_any")
{
CheckResult result = check(R"(
--!nonstrict
local T = {}
function T:method() end
function T.staticmethod() end
T.method()
T:staticmethod()
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("This function does not take self. Did you mean to use a dot instead of a colon?", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "offer_a_hint_if_you_use_a_dot_instead_of_a_colon")
{
CheckResult result = check(R"(
--!nonstrict
local T = {}
function T:method(x: number) end
T.method(5)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("This function must be called with self. Did you mean to use a colon instead of a dot?", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "table_props_are_any")
{
CheckResult result = check(R"(
--!nonstrict
local T = {}
T.foo = 55
)");
LUAU_REQUIRE_NO_ERRORS(result);
TableType* ttv = getMutable<TableType>(requireType("T"));
REQUIRE(ttv != nullptr);
REQUIRE(ttv->props.count("foo"));
TypeId fooProp = ttv->props["foo"].type;
REQUIRE(fooProp != nullptr);
CHECK_EQ(*fooProp, *typeChecker.anyType);
}
TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any")
{
CheckResult result = check(R"(
--!nonstrict
local T = {
one = 1,
two = 'two',
three = function() return 3 end
}
)");
LUAU_REQUIRE_NO_ERRORS(result);
TableType* ttv = getMutable<TableType>(requireType("T"));
REQUIRE_MESSAGE(ttv, "Should be a table: " << toString(requireType("T")));
CHECK_EQ(*typeChecker.anyType, *ttv->props["one"].type);
CHECK_EQ(*typeChecker.anyType, *ttv->props["two"].type);
CHECK_MESSAGE(get<FunctionType>(follow(ttv->props["three"].type)), "Should be a function: " << *ttv->props["three"].type);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_iterator_variables_are_any")
{
CheckResult result = check(R"(
--!nonstrict
function requires_a_table(arg: {}) end
function requires_a_number(arg: number) end
local T = {}
for a, b in pairs(T) do
requires_a_table(a)
requires_a_table(b)
requires_a_number(a)
requires_a_number(b)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_insert_and_recursive_calls")
{
CheckResult result = check(R"(
--!nonstrict
function populateListFromIds(list, normalizedData)
local newList = {}
for _, value in ipairs(list) do
if type(value) == "table" then
table.insert(newList, populateListFromIds(value, normalizedData))
else
table.insert(newList, normalizedData[value])
end
end
return newList
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "delay_function_does_not_require_its_argument_to_return_anything")
{
CheckResult result = check(R"(
--!nonstrict
function delay(ms: number?, cb: () -> ()): () end
delay(50, function() end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "inconsistent_module_return_types_are_ok")
{
CheckResult result = check(R"(
--!nonstrict
local FFlag: any
if FFlag.get('SomeFlag') then
return {foo='bar'}
else
return function(prop)
return 'bar'
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
REQUIRE_EQ("any", toString(getMainModule()->returnType));
}
TEST_CASE_FIXTURE(Fixture, "returning_insufficient_return_values")
{
CheckResult result = check(R"(
--!nonstrict
function foo(): (boolean, string?)
if true then
return true, "hello"
else
return false
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "returning_too_many_values")
{
CheckResult result = check(R"(
--!nonstrict
function foo(): boolean
if true then
return true, "hello"
else
return false
end
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();