mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
d2ab5df62b
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>
310 lines
7.1 KiB
C++
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();
|