luau/tests/TypeInfer.builtins.test.cpp

1447 lines
43 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/TypeInfer.h"
#include "Luau/BuiltinDefinitions.h"
#include "Fixture.h"
#include "doctest.h"
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
2022-04-15 00:57:43 +01:00
TEST_SUITE_BEGIN("BuiltinTests");
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "math_things_are_defined")
{
CheckResult result = check(R"(
local a00 = math.frexp
local a01 = math.ldexp
local a02 = math.fmod
local a03 = math.modf
local a04 = math.pow
local a05 = math.exp
local a06 = math.floor
local a07 = math.abs
local a08 = math.sqrt
local a09 = math.log
local a10 = math.log10
local a11 = math.rad
local a12 = math.deg
local a13 = math.sin
local a14 = math.cos
local a15 = math.tan
local a16 = math.sinh
local a17 = math.cosh
local a18 = math.tanh
local a19 = math.atan
local a20 = math.acos
local a21 = math.asin
local a22 = math.atan2
local a23 = math.ceil
local a24 = math.min
local a25 = math.max
local a26 = math.pi
local a29 = math.huge
local a30 = math.randomseed
local a31 = math.random
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "next_iterator_should_infer_types_and_type_check")
{
CheckResult result = check(R"(
local a: string, b: number = next({ 1 })
local s = "foo"
local t = { [s] = 1 }
local c: string?, d: number = next(t)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "pairs_iterator_should_infer_types_and_type_check")
{
CheckResult result = check(R"(
type Map<K, V> = { [K]: V }
local map: Map<string, number> = { ["foo"] = 1, ["bar"] = 2, ["baz"] = 3 }
local it: (Map<string, number>, string | nil) -> (string?, number), t: Map<string, number>, i: nil = pairs(map)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_iterator_should_infer_types_and_type_check")
{
CheckResult result = check(R"(
type Map<K, V> = { [K]: V }
local array: Map<number, string> = { "foo", "bar", "baz" }
local it: (Map<number, string>, number) -> (number?, string), t: Map<number, string>, i: number = ipairs(array)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_remove_optionally_returns_generic")
{
CheckResult result = check(R"(
local t = { 1 }
local n = table.remove(t, 7)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("n")), "number?");
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_concat_returns_string")
{
CheckResult result = check(R"(
local r = table.concat({1,2,3,4}, ",", 2);
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->stringType, *requireType("r"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "sort")
{
CheckResult result = check(R"(
local t = {1, 2, 3};
table.sort(t)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_predicate")
{
CheckResult result = check(R"(
--!strict
local t = {1, 2, 3}
local function p(a: number, b: number) return a < b end
table.sort(t, p)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
CheckResult result = check(R"(
--!strict
local t = {'one', 'two', 'three'}
local function p(a: number, b: number) return a < b end
table.sort(t, p)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type
'(number, number) -> boolean'
could not be converted into
'((string, string) -> boolean)?'
2022-05-13 20:36:37 +01:00
caused by:
None of the union options are compatible. For example:
Type
'(number, number) -> boolean'
could not be converted into
'(string, string) -> boolean'
2022-05-13 20:36:37 +01:00
caused by:
Argument #1 type is not compatible.
Type 'string' could not be converted into 'number')";
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "strings_have_methods")
{
CheckResult result = check(R"LUA(
local s = ("RoactHostChangeEvent(%s)"):format("hello")
)LUA");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->stringType, *requireType("s"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_variatic")
{
CheckResult result = check(R"(
local n = math.max(1,2,3,4,5,6,7,8,9,0)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "math_max_checks_for_numbers")
{
CheckResult result = check(R"(
local n = math.max(1,2,"3")
)");
LUAU_REQUIRE_ERRORS(result);
2022-05-13 20:36:37 +01:00
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_tables_sealed")
{
CheckResult result = check(R"LUA(
local b = bit32
)LUA");
TypeId bit32 = requireType("b");
REQUIRE(bit32 != nullptr);
const TableType* bit32t = get<TableType>(bit32);
REQUIRE(bit32t != nullptr);
CHECK_EQ(bit32t->state, TableState::Sealed);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "lua_51_exported_globals_all_exist")
{
// Extracted from lua5.1
CheckResult result = check(R"(
local v__G = _G
local v_string_sub = string.sub
local v_string_upper = string.upper
local v_string_len = string.len
local v_string_rep = string.rep
local v_string_find = string.find
local v_string_match = string.match
local v_string_char = string.char
local v_string_gmatch = string.gmatch
local v_string_reverse = string.reverse
local v_string_byte = string.byte
local v_string_format = string.format
local v_string_gsub = string.gsub
local v_string_lower = string.lower
local v_xpcall = xpcall
--local v_package_loadlib = package.loadlib
--local v_package_loaders_1_ = package.loaders[1]
--local v_package_loaders_2_ = package.loaders[2]
--local v_package_loaders_3_ = package.loaders[3]
--local v_package_loaders_4_ = package.loaders[4]
local v_tostring = tostring
local v_print = print
--local v_os_exit = os.exit
--local v_os_setlocale = os.setlocale
local v_os_date = os.date
--local v_os_getenv = os.getenv
local v_os_difftime = os.difftime
--local v_os_remove = os.remove
local v_os_time = os.time
--local v_os_clock = os.clock
--local v_os_tmpname = os.tmpname
--local v_os_rename = os.rename
--local v_os_execute = os.execute
local v_unpack = unpack
local v_require = require
local v_getfenv = getfenv
local v_setmetatable = setmetatable
local v_next = next
local v_assert = assert
local v_tonumber = tonumber
--local v_io_lines = io.lines
--local v_io_write = io.write
--local v_io_close = io.close
--local v_io_flush = io.flush
--local v_io_open = io.open
--local v_io_output = io.output
--local v_io_type = io.type
--local v_io_read = io.read
--local v_io_stderr = io.stderr
--local v_io_stdin = io.stdin
--local v_io_input = io.input
--local v_io_stdout = io.stdout
--local v_io_popen = io.popen
--local v_io_tmpfile = io.tmpfile
local v_rawequal = rawequal
--local v_collectgarbage = collectgarbage
local v_getmetatable = getmetatable
local v_rawset = rawset
local v_math_log = math.log
local v_math_max = math.max
local v_math_acos = math.acos
local v_math_huge = math.huge
local v_math_ldexp = math.ldexp
local v_math_pi = math.pi
local v_math_cos = math.cos
local v_math_tanh = math.tanh
local v_math_pow = math.pow
local v_math_deg = math.deg
local v_math_tan = math.tan
local v_math_cosh = math.cosh
local v_math_sinh = math.sinh
local v_math_random = math.random
local v_math_randomseed = math.randomseed
local v_math_frexp = math.frexp
local v_math_ceil = math.ceil
local v_math_floor = math.floor
local v_math_rad = math.rad
local v_math_abs = math.abs
local v_math_sqrt = math.sqrt
local v_math_modf = math.modf
local v_math_asin = math.asin
local v_math_min = math.min
--local v_math_mod = math.mod
local v_math_fmod = math.fmod
local v_math_log10 = math.log10
local v_math_atan2 = math.atan2
local v_math_exp = math.exp
local v_math_sin = math.sin
local v_math_atan = math.atan
--local v_debug_getupvalue = debug.getupvalue
--local v_debug_debug = debug.debug
--local v_debug_sethook = debug.sethook
--local v_debug_getmetatable = debug.getmetatable
--local v_debug_gethook = debug.gethook
--local v_debug_setmetatable = debug.setmetatable
--local v_debug_setlocal = debug.setlocal
--local v_debug_traceback = debug.traceback
--local v_debug_setfenv = debug.setfenv
--local v_debug_getinfo = debug.getinfo
--local v_debug_setupvalue = debug.setupvalue
--local v_debug_getlocal = debug.getlocal
--local v_debug_getregistry = debug.getregistry
--local v_debug_getfenv = debug.getfenv
local v_pcall = pcall
--local v_table_setn = table.setn
local v_table_insert = table.insert
--local v_table_getn = table.getn
--local v_table_foreachi = table.foreachi
local v_table_maxn = table.maxn
--local v_table_foreach = table.foreach
local v_table_concat = table.concat
local v_table_sort = table.sort
local v_table_remove = table.remove
local v_newproxy = newproxy
local v_type = type
local v_coroutine_resume = coroutine.resume
local v_coroutine_yield = coroutine.yield
local v_coroutine_status = coroutine.status
local v_coroutine_wrap = coroutine.wrap
local v_coroutine_create = coroutine.create
local v_coroutine_running = coroutine.running
local v_select = select
local v_gcinfo = gcinfo
local v_pairs = pairs
local v_rawget = rawget
local v_loadstring = loadstring
local v_ipairs = ipairs
local v__VERSION = _VERSION
--local v_dofile = dofile
local v_setfenv = setfenv
--local v_load = load
local v_error = error
--local v_loadfile = loadfile
)");
dumpErrors(result);
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly")
{
CheckResult result = check(R"(
setmetatable({}, setmetatable({}, {}))
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
{
CheckResult result = check(R"(
type A = {tag: "A", x: number}
type B = {tag: "B", y: string}
type T = A | B
type X = typeof(
setmetatable({} :: T, {})
)
)");
LUAU_REQUIRE_NO_ERRORS(result);
Sync to upstream/release/614 (#1173) # What's changed? Add program argument passing to scripts run using the Luau REPL! You can now pass `--program-args` (or shorthand `-a`) to the REPL which will treat all remaining arguments as arguments to pass to executed scripts. These values can be accessed through variadic argument expansion. You can read these values like so: ``` local args = {...} -- gets you an array of all the arguments ``` For example if we run the following script like `luau test.lua -a test1 test2 test3`: ``` -- test.lua print(...) ``` you should get the output: ``` test1 test2 test3 ``` ### Native Code Generation * Improve A64 lowering for vector operations by using vector instructions * Fix lowering issue in IR value location tracking! - A developer reported a divergence between code run in the VM and Native Code Generation which we have now fixed ### New Type Solver * Apply substitution to type families, and emit new constraints to reduce those further * More progress on reducing comparison (`lt/le`)type families * Resolve two major sources of cyclic types in the new solver ### Miscellaneous * Turned internal compiler errors (ICE's) into warnings and errors ------- 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: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- 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: 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>
2024-02-23 20:08:34 +00:00
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload")
{
CheckResult result = check(R"(
local t = {}
table.insert(t, "foo")
local s = t[1]
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(builtinTypes->stringType, requireType("s"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_3_args_overload")
{
CheckResult result = check(R"(
local t = {}
table.insert(t, 1, "foo")
local s = t[1]
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("string", toString(requireType("s")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack")
{
CheckResult result = check(R"(
local t = table.pack(1, "foo", true)
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ [number]: boolean | number | string, n: number }", toString(requireType("t")));
else
CHECK_EQ("{| [number]: boolean | number | string, n: number |}", toString(requireType("t")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_variadic")
{
CheckResult result = check(R"(
--!strict
function f(): (string, ...number)
return "str", 2, 3, 4
end
local t = table.pack(f())
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ [number]: number | string, n: number }", toString(requireType("t")));
else
CHECK_EQ("{| [number]: number | string, n: number |}", toString(requireType("t")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
{
CheckResult result = check(R"(
local t = table.pack(1, 2, true)
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ [number]: boolean | number, n: number }", toString(requireType("t")));
else
CHECK_EQ("{| [number]: boolean | number, n: number |}", toString(requireType("t")));
result = check(R"(
local t = table.pack("a", "b", "c")
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));
else
CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "gcinfo")
{
CheckResult result = check(R"(
local n = gcinfo()
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("n"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "getfenv")
{
LUAU_REQUIRE_NO_ERRORS(check("getfenv(1)"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "os_time_takes_optional_date_table")
{
CheckResult result = check(R"(
local n1 = os.time()
local n2 = os.time({ year = 2020, month = 4, day = 20 })
local n3 = os.time({ year = 2020, month = 4, day = 20, hour = 0, min = 0, sec = 0, isdst = true })
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(*builtinTypes->numberType, *requireType("n1"));
CHECK_EQ(*builtinTypes->numberType, *requireType("n2"));
CHECK_EQ(*builtinTypes->numberType, *requireType("n3"));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "thread_is_a_type")
{
2022-03-11 16:55:02 +00:00
CheckResult result = check(R"(
local co = coroutine.create(function() end)
)");
2022-03-11 16:55:02 +00:00
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("thread" == toString(requireType("co")));
}
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 21:10:07 +00:00
TEST_CASE_FIXTURE(BuiltinsFixture, "buffer_is_a_type")
{
CheckResult result = check(R"(
local b = buffer.create(10)
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("buffer" == toString(requireType("b")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_resume_anything_goes")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
2022-03-11 16:55:02 +00:00
CheckResult result = check(R"(
local function nifty(x, y)
print(x, y)
local z = coroutine.yield(1, 2)
print(z)
return 42
end
local co = coroutine.create(nifty)
local x, y = coroutine.resume(co, 1, 2)
local answer = coroutine.resume(co, 3)
)");
2022-03-11 16:55:02 +00:00
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "coroutine_wrap_anything_goes")
{
2022-03-11 16:55:02 +00:00
CheckResult result = check(R"(
--!nonstrict
local function nifty(x, y)
print(x, y)
local z = coroutine.yield(1, 2)
print(z)
return 42
end
local f = coroutine.wrap(nifty)
local x, y = f(1, 2)
local answer = f(3)
)");
2022-03-11 16:55:02 +00:00
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_should_not_mutate_persisted_types")
{
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local string = string
setmetatable(string, {})
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
auto stringType = requireType("string");
auto ttv = get<TableType>(stringType);
REQUIRE(ttv);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_arg_types_inference")
{
CheckResult result = check(R"(
--!strict
function f(a, b, c)
return string.format("%f %d %s", a, b, c)
end
)");
CHECK_EQ(0, result.errors.size());
CHECK_EQ("(number, number, string) -> string", toString(requireType("f")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_arg_count_mismatch")
{
CheckResult result = check(R"(
--!strict
string.format("%f %d %s")
string.format("%s", "hi", 42)
string.format("%s", "hi", 42, ...)
string.format("%s", "hi", ...)
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK_EQ(result.errors[0].location.begin.line, 2);
CHECK_EQ(result.errors[1].location.begin.line, 3);
CHECK_EQ(result.errors[2].location.begin.line, 4);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_correctly_ordered_types")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
--!strict
string.format("%s", 123)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(tm->wantedType, builtinTypes->stringType);
CHECK_EQ(tm->givenType, builtinTypes->numberType);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_tostring_specifier")
{
CheckResult result = check(R"(
--!strict
string.format("%* %* %* %*", "string", 1, true, function() end)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_tostring_specifier_type_constraint")
{
CheckResult result = check(R"(
local function f(x): string
local _ = string.format("%*", x)
return x
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("(string) -> string", toString(requireType("f")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall")
{
CheckResult result = check(R"(
--!strict
local a, b, c = xpcall(
function() return 5, true end,
function(e) return 0, false end
)
)");
LUAU_REQUIRE_NO_ERRORS(result);
2022-04-15 00:57:43 +01:00
CHECK_EQ("boolean", toString(requireType("a")));
CHECK_EQ("number", toString(requireType("b")));
CHECK_EQ("boolean", toString(requireType("c")));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "trivial_select")
{
CheckResult result = check(R"(
local a:number = select(1, 42)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "see_thru_select")
{
CheckResult result = check(R"(
local a:number, b:boolean = select(2,"hi", 10, true)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "see_thru_select_count")
{
CheckResult result = check(R"(
local a = select("#","hi", 10, true)
)");
dumpErrors(result);
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_decimal_argument_is_rounded_down")
{
CheckResult result = check(R"(
local a: number, b: boolean = select(2.9, "foo", 1, true)
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
// Could be flaky if the fix has regressed.
TEST_CASE_FIXTURE(BuiltinsFixture, "bad_select_should_not_crash")
{
CheckResult result = check(R"(
do end
local _ = function(l0,...)
end
local _ = function()
_(_);
_ += select(_())
end
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// Counterintuitively, the parameter l0 is unconstrained and therefore it is valid to pass nil.
// The new solver therefore considers that parameter to be optional.
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK("Argument count mismatch. Function expects at least 1 argument, but none are specified" == toString(result.errors[0]));
}
else
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Argument count mismatch. Function '_' expects at least 1 argument, but none are specified", toString(result.errors[0]));
CHECK_EQ("Argument count mismatch. Function 'select' expects 1 argument, but none are specified", toString(result.errors[1]));
}
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_way_out_of_range")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
select(5432598430953240958)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<GenericError>(result.errors[0]));
CHECK_EQ("bad argument #1 to select (index out of range)", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_slightly_out_of_range")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
select(3, "a", 1)
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
REQUIRE(get<GenericError>(result.errors[0]));
CHECK_EQ("bad argument #1 to select (index out of range)", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail")
{
CheckResult result = check(R"(
--!nonstrict
local function f(...)
return ...
end
local foo, bar, baz, quux = select(1, f("foo", true))
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("any", toString(requireType("foo")));
CHECK_EQ("any", toString(requireType("bar")));
CHECK_EQ("any", toString(requireType("baz")));
CHECK_EQ("any", toString(requireType("quux")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_with_variadic_typepack_tail_and_string_head")
{
// CLI-115720
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
--!nonstrict
local function f(...)
return ...
end
local foo, bar, baz, quux = select(1, "foo", f("bar", true))
)");
LUAU_REQUIRE_NO_ERRORS(result);
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 19:20:37 +00:00
CHECK_EQ("any", toString(requireType("foo")));
CHECK_EQ("any", toString(requireType("bar")));
CHECK_EQ("any", toString(requireType("baz")));
CHECK_EQ("any", toString(requireType("quux")));
}
TEST_CASE_FIXTURE(Fixture, "string_format_as_method")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check("local _ = ('%s'):format(5)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(tm->wantedType, builtinTypes->stringType);
CHECK_EQ(tm->givenType, builtinTypes->numberType);
}
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument")
{
CheckResult result = check(R"(
local _ = ("%s"):format("%d", "hello")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Argument count mismatch. Function expects 2 arguments, but 3 are specified", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "string_format_use_correct_argument2")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local _ = ("%s %d").format("%d %s", "A type error", 2)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
CHECK_EQ("Type 'number' could not be converted into 'string'", toString(result.errors[1]));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_use_correct_argument3")
{
CheckResult result = check(R"(
local s1 = string.format("%d")
local s2 = string.format("%d", 1)
local s3 = string.format("%d", 1, 2)
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("Argument count mismatch. Function expects 2 arguments, but only 1 is specified", toString(result.errors[0]));
CHECK_EQ("Argument count mismatch. Function expects 2 arguments, but 3 are specified", toString(result.errors[1]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "debug_traceback_is_crazy")
{
CheckResult result = check(R"(
function f(co: thread)
-- debug.traceback takes thread?, message?, level? - yes, all optional!
debug.traceback()
debug.traceback(nil, 1)
debug.traceback("msg")
debug.traceback("msg", 1)
debug.traceback(co)
debug.traceback(co, "msg")
debug.traceback(co, "msg", 1)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "debug_info_is_crazy")
{
CheckResult result = check(R"(
function f(co: thread, f: () -> ())
-- debug.info takes thread?, level, options or function, options
debug.info(1, "n")
debug.info(co, 1, "n")
debug.info(f, "n")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "aliased_string_format")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local fmt = string.format
local s = fmt("%d", "oops")
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "string_lib_self_noself")
{
CheckResult result = check(R"(
--!nonstrict
local a1 = string.byte("abcdef", 2)
local a2 = string.find("abcdef", "def")
local a3 = string.gmatch("ab ab", "%a+")
local a4 = string.gsub("abab", "ab", "cd")
local a5 = string.len("abc")
local a6 = string.match("12 ab", "%d+ %a+")
local a7 = string.rep("a", 10)
local a8 = string.sub("abcd", 1, 2)
local a9 = string.split("a,b,c", ",")
local a0 = string.packsize("ff")
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_definition")
{
CheckResult result = check(R"_(
local a, b, c = ("hey"):gmatch("(.)(.)(.)")()
for c in ("hey"):gmatch("(.)") do
print(c:upper())
end
)_");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "select_on_variadic")
{
CheckResult result = check(R"(
local function f(): (number, ...(boolean | number))
return 100, true, 1
end
local a, b, c = select(f())
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("any", toString(requireType("a")));
CHECK_EQ("any", toString(requireType("b")));
CHECK_EQ("any", toString(requireType("c")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_report_all_type_errors_at_correct_positions")
{
// CLI-115690
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
("%s%d%s"):format(1, "hello", true)
string.format("%s%d%s", 1, "hello", true)
)");
TypeId stringType = builtinTypes->stringType;
TypeId numberType = builtinTypes->numberType;
TypeId booleanType = builtinTypes->booleanType;
LUAU_REQUIRE_ERROR_COUNT(6, result);
CHECK_EQ(Location(Position{1, 26}, Position{1, 27}), result.errors[0].location);
CHECK_EQ(TypeErrorData(TypeMismatch{stringType, numberType}), result.errors[0].data);
CHECK_EQ(Location(Position{1, 29}, Position{1, 36}), result.errors[1].location);
CHECK_EQ(TypeErrorData(TypeMismatch{numberType, stringType}), result.errors[1].data);
CHECK_EQ(Location(Position{1, 38}, Position{1, 42}), result.errors[2].location);
CHECK_EQ(TypeErrorData(TypeMismatch{stringType, booleanType}), result.errors[2].data);
CHECK_EQ(Location(Position{2, 32}, Position{2, 33}), result.errors[3].location);
CHECK_EQ(TypeErrorData(TypeMismatch{stringType, numberType}), result.errors[3].data);
CHECK_EQ(Location(Position{2, 35}, Position{2, 42}), result.errors[4].location);
CHECK_EQ(TypeErrorData(TypeMismatch{numberType, stringType}), result.errors[4].data);
CHECK_EQ(Location(Position{2, 44}, Position{2, 48}), result.errors[5].location);
CHECK_EQ(TypeErrorData(TypeMismatch{stringType, booleanType}), result.errors[5].data);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type")
{
CheckResult result = check(R"(
--!strict
local b: number = tonumber('asdf')
)");
2022-02-11 19:02:09 +00:00
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(
"Type 'number?' could not be converted into 'number'; type number?[1] (nil) is not a subtype of number (number)",
toString(result.errors[0])
);
else
CHECK_EQ("Type 'number?' could not be converted into 'number'", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "tonumber_returns_optional_number_type2")
{
CheckResult result = check(R"(
--!strict
local b: number = tonumber('asdf') or 1
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_add_definitions_to_persistent_types")
{
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 21:29:06 +00:00
// This test makes no sense with type states and I think it generally makes no sense under the new solver.
// TODO: clip.
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
local f = math.sin
local function g(x) return math.sin(x) end
f = g
)");
LUAU_REQUIRE_NO_ERRORS(result);
TypeId fType = requireType("f");
const FunctionType* ftv = get<FunctionType>(fType);
REQUIRE(fType);
REQUIRE(fType->persistent);
REQUIRE(!ftv->definition);
TypeId gType = requireType("g");
const FunctionType* gtv = get<FunctionType>(gType);
REQUIRE(gType);
REQUIRE(!gType->persistent);
REQUIRE(gtv->definition);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types")
2022-02-04 16:45:57 +00:00
{
CheckResult result = check(R"(
local function f(x: (number | boolean)?)
return assert(x)
end
)");
2022-03-18 00:46:04 +00:00
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
else
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
2022-03-18 00:46:04 +00:00
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
2022-03-18 00:46:04 +00:00
{
CheckResult result = check(R"(
local function f(x: (number | boolean)?): number | true
return assert(x)
end
)");
2022-02-04 16:45:57 +00:00
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
}
Sync to upstream/release/614 (#1173) # What's changed? Add program argument passing to scripts run using the Luau REPL! You can now pass `--program-args` (or shorthand `-a`) to the REPL which will treat all remaining arguments as arguments to pass to executed scripts. These values can be accessed through variadic argument expansion. You can read these values like so: ``` local args = {...} -- gets you an array of all the arguments ``` For example if we run the following script like `luau test.lua -a test1 test2 test3`: ``` -- test.lua print(...) ``` you should get the output: ``` test1 test2 test3 ``` ### Native Code Generation * Improve A64 lowering for vector operations by using vector instructions * Fix lowering issue in IR value location tracking! - A developer reported a divergence between code run in the VM and Native Code Generation which we have now fixed ### New Type Solver * Apply substitution to type families, and emit new constraints to reduce those further * More progress on reducing comparison (`lt/le`)type families * Resolve two major sources of cyclic types in the new solver ### Miscellaneous * Turned internal compiler errors (ICE's) into warnings and errors ------- 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: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- 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: 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>
2024-02-23 20:08:34 +00:00
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types3")
{
CheckResult result = check(R"(
local function f(x: (number | boolean)?)
assert(x)
return x
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
else // without the annotation, the old solver doesn't infer the best return type here
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
2022-02-04 16:45:57 +00:00
{
if (FFlag::DebugLuauDeferredConstraintResolution)
return;
2022-02-04 16:45:57 +00:00
CheckResult result = check(R"(
local function f(...: number?)
return assert(...)
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("(...number?) -> (number, ...number?)", toString(requireType("f")));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy")
2022-02-04 16:45:57 +00:00
{
if (FFlag::DebugLuauDeferredConstraintResolution)
{
// CLI-114134 - egraph simplification
return;
}
2022-02-04 16:45:57 +00:00
CheckResult result = check(R"(
local function f(x: nil)
return assert(x, "hmm")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
2022-07-08 02:22:39 +01:00
CHECK_EQ("(nil) -> (never, ...never)", toString(requireType("f")));
2022-02-04 16:45:57 +00:00
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic")
2022-03-04 16:36:33 +00:00
{
CheckResult result = check(R"(
local t1: {a: number} = {a = 42}
local t2: {b: string} = {b = "hello"}
local t3: {boolean} = {false, true}
local tf1 = table.freeze(t1)
local tf2 = table.freeze(t2)
local tf3 = table.freeze(t3)
local a = tf1.a
local b = tf2.b
local c = tf3[2]
local d = tf1.b
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("Key 'b' not found in table '{ a: number }'" == toString(result.errors[0]));
else
CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0]));
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 19:20:37 +00:00
CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location);
2022-03-04 16:36:33 +00:00
CHECK_EQ("number", toString(requireType("a")));
CHECK_EQ("string", toString(requireType("b")));
CHECK_EQ("boolean", toString(requireType("c")));
Sync to upstream/release/568 (#865) * A small subset of control-flow refinements have been added to recognize type options that are unreachable after a conditional/unconditional code block. (Fixes https://github.com/Roblox/luau/issues/356). Some examples: ```lua local function f(x: string?) if not x then return end -- x is 'string' here end ``` Throwing calls like `error` or `assert(false)` instead of 'return' are also recognized. Existing complex refinements like type/typeof and tagged union checks are expected to work, among others. To enable this feature, `LuauTinyControlFlowAnalysis` exclusion has to be removed from `ExperimentalFlags.h`. If will become enabled unconditionally in the near future. * Linter has been integrated into the typechecker analysis so that type-aware lint warnings can work in any mode `Frontend::lint` methods were deprecated, `Frontend::check` has to be used instead with `runLintChecks` option set. Resulting lint warning are located inside `CheckResult`. * Fixed large performance drop and increased memory consumption when array is filled at an offset (Fixes https://github.com/Roblox/luau/issues/590) * Part of [Type error suppression RFC](https://github.com/Roblox/luau/blob/master/rfcs/type-error-suppression.md) was implemented making subtyping checks with `any` type transitive. --- In our work on the new type-solver: * `--!nocheck` mode no longer reports type errors * New solver will not be used for `--!nonstrict` modules until all issues with strict mode typechecking are fixed * Added control-flow aware type refinements mentioned earlier In native code generation: * `LOP_NAMECALL` has been translated to IR * `type` and `typeof` builtin fastcalls have been translated to IR/assembly * Additional steps were taken towards arm64 support
2023-03-17 19:20:37 +00:00
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("any", toString(requireType("d")));
else
CHECK_EQ("*error-type*", toString(requireType("d")));
2022-03-04 16:36:33 +00:00
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "set_metatable_needs_arguments")
2022-03-11 16:55:02 +00:00
{
// In the new solver, nil can certainly be used where a generic is required, so all generic parameters are optional.
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
2022-03-11 16:55:02 +00:00
CheckResult result = check(R"(
local a = {b=setmetatable}
a.b()
a:b()
a:b({})
2022-03-11 16:55:02 +00:00
)");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Argument count mismatch. Function 'a.b' expects 2 arguments, but none are specified");
CHECK_EQ(toString(result.errors[1]), "Argument count mismatch. Function 'a.b' expects 2 arguments, but only 1 is specified");
2022-03-11 16:55:02 +00:00
}
2022-03-18 00:46:04 +00:00
TEST_CASE_FIXTURE(Fixture, "typeof_unresolved_function")
{
CheckResult result = check(R"(
local function f(a: typeof(f)) end
)");
2022-03-18 00:46:04 +00:00
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Unknown global 'f'", toString(result.errors[0]));
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "no_persistent_typelevel_change")
2022-03-18 00:46:04 +00:00
{
TypeId mathTy = requireType(frontend.globals.globalScope, "math");
2022-03-18 00:46:04 +00:00
REQUIRE(mathTy);
TableType* ttv = getMutable<TableType>(mathTy);
2022-03-18 00:46:04 +00:00
REQUIRE(ttv);
const FunctionType* ftv = get<FunctionType>(ttv->props["frexp"].type());
2022-03-18 00:46:04 +00:00
REQUIRE(ftv);
auto original = ftv->level;
CheckResult result = check("local a = math.frexp");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(ftv->level.level == original.level);
CHECK(ftv->level.subLevel == original.subLevel);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "global_singleton_types_are_sealed")
2022-03-18 00:46:04 +00:00
{
CheckResult result = check(R"(
local function f(x: string)
local p = x:split('a')
p = table.pack(table.unpack(p, 1, #p - 1))
return p
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "string_match")
{
CheckResult result = check(R"(
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 21:29:06 +00:00
local s: string = "hello"
local p = s:match("foo")
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("p")), "string?");
}
2022-07-08 02:22:39 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types")
{
CheckResult result = check(R"END(
local a, b, c = string.gmatch("This is a string", "(.()(%a+))")()
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(Fixture, "gmatch_capture_types2")
2022-07-08 02:22:39 +01:00
{
CheckResult result = check(R"END(
local a, b, c = ("This is a string"):gmatch("(.()(%a+))")()
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_default_capture")
{
CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his)() is a string", ".")()
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
2022-07-08 02:22:39 +01:00
CHECK_EQ(acm->expected, 1);
CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("a")), "string?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_balanced_escaped_parens")
{
CheckResult result = check(R"END(
local a, b, c, d = string.gmatch("T(his) is a string", "((.)%b()())")()
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
2022-07-08 02:22:39 +01:00
CHECK_EQ(acm->expected, 3);
CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "string?");
CHECK_EQ(toString(requireType("c")), "number?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_parens_in_sets_are_ignored")
{
CheckResult result = check(R"END(
local a, b, c = string.gmatch("T(his)() is a string", "(T[()])()")()
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
2022-07-08 02:22:39 +01:00
CHECK_EQ(acm->expected, 2);
CHECK_EQ(acm->actual, 3);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_set_containing_lbracket")
{
CheckResult result = check(R"END(
local a, b = string.gmatch("[[[", "()([[])")()
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "number?");
CHECK_EQ(toString(requireType("b")), "string?");
2022-07-08 02:22:39 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_leading_end_bracket_is_part_of_set")
{
CheckResult result = check(R"END(
-- An immediate right-bracket following a left-bracket is included within the set;
-- thus, '[]]'' is the set containing ']', and '[]' is an invalid set missing an enclosing
-- right-bracket. We detect an invalid set in this case and fall back to to default gmatch
-- typing.
local foo = string.gmatch("T[hi%]s]]]() is a string", "([]s)")
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallback_to_builtin")
{
CheckResult result = check(R"END(
local foo = string.gmatch("T(his)() is a string", ")")
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "gmatch_capture_types_invalid_pattern_fallback_to_builtin2")
{
CheckResult result = check(R"END(
local foo = string.gmatch("T(his)() is a string", "[")
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("foo")), "() -> (...string)");
}
2022-07-14 23:52:26 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types")
{
CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))")
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-14 23:52:26 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "match_capture_types2")
{
CheckResult result = check(R"END(
local a, b, c = string.match("This is a string", "(.()(%a+))", "this should be a number")
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(toString(tm->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-14 23:52:26 +01:00
}
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types")
{
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))")
)END");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-14 23:52:26 +01:00
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types2")
{
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", "this should be a number")
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(toString(tm->wantedType), "number?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-14 23:52:26 +01:00
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{
CheckResult result = check(R"END(
local d, e, a, b, c = string.find("This is a string", "(.()(%a+))", 1, "this should be a bool")
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
REQUIRE(tm);
CHECK_EQ(toString(tm->wantedType), "boolean?");
CHECK_EQ(toString(tm->givenType), "string");
CHECK_EQ(toString(requireType("a")), "string?");
CHECK_EQ(toString(requireType("b")), "number?");
CHECK_EQ(toString(requireType("c")), "string?");
2022-07-14 23:52:26 +01:00
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "find_capture_types3")
{
CheckResult result = check(R"END(
local d, e, a, b = string.find("This is a string", "(.()(%a+))", 1, true)
)END");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CountMismatch* acm = get<CountMismatch>(result.errors[0]);
REQUIRE(acm);
CHECK_EQ(acm->context, CountMismatch::FunctionResult);
2022-07-14 23:52:26 +01:00
CHECK_EQ(acm->expected, 2);
CHECK_EQ(acm->actual, 4);
CHECK_EQ(toString(requireType("d")), "number?");
CHECK_EQ(toString(requireType("e")), "number?");
}
TEST_SUITE_END();