luau/tests/Linter.test.cpp

2029 lines
56 KiB
C++
Raw Permalink 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/Linter.h"
#include "Luau/BuiltinDefinitions.h"
#include "Fixture.h"
#include "ScopedFlags.h"
#include "doctest.h"
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNativeAttribute);
LUAU_FASTFLAG(LintRedundantNativeAttribute);
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
using namespace Luau;
TEST_SUITE_BEGIN("Linter");
TEST_CASE_FIXTURE(Fixture, "CleanCode")
{
LintResult result = lint(R"(
function fib(n)
return n < 2 and 1 or fib(n-1) + fib(n-2)
end
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "type_function_fully_reduces")
{
LintResult result = lint(R"(
function fib(n)
return n < 2 or fib(n-2)
end
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "UnknownGlobal")
{
LintResult result = lint("--!nocheck\nreturn foo");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Unknown global 'foo'");
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobal")
{
// Normally this would be defined externally, so hack it in for testing
addGlobalBinding(frontend.globals, "Wait", Binding{builtinTypes->anyType, {}, true, "wait", "@test/global/Wait"});
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
LintResult result = lint("Wait(5)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Global 'Wait' is deprecated, use 'wait' instead");
}
TEST_CASE_FIXTURE(Fixture, "DeprecatedGlobalNoReplacement")
{
// Normally this would be defined externally, so hack it in for testing
const char* deprecationReplacementString = "";
addGlobalBinding(frontend.globals, "Version", Binding{builtinTypes->anyType, {}, true, deprecationReplacementString});
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
LintResult result = lint("Version()");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Global 'Version' is deprecated");
}
TEST_CASE_FIXTURE(Fixture, "PlaceholderRead")
{
LintResult result = lint(R"(
local _ = 5
return _
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Placeholder value '_' is read here; consider using a named variable");
}
2022-02-04 16:45:57 +00:00
TEST_CASE_FIXTURE(Fixture, "PlaceholderReadGlobal")
{
LintResult result = lint(R"(
_ = 5
print(_)
)");
REQUIRE(1 == result.warnings.size());
2022-02-04 16:45:57 +00:00
CHECK_EQ(result.warnings[0].text, "Placeholder value '_' is read here; consider using a named variable");
}
TEST_CASE_FIXTURE(Fixture, "PlaceholderWrite")
{
LintResult result = lint(R"(
local _ = 5
_ = 6
)");
REQUIRE(0 == result.warnings.size());
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "BuiltinGlobalWrite")
{
LintResult result = lint(R"(
math = {}
function assert(x)
end
assert(5)
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Built-in global 'math' is overwritten here; consider using a local or changing the name");
CHECK_EQ(result.warnings[1].text, "Built-in global 'assert' is overwritten here; consider using a local or changing the name");
}
TEST_CASE_FIXTURE(Fixture, "MultilineBlock")
{
LintResult result = lint(R"(
if true then print(1) print(2) print(3) end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "A new statement is on the same line; add semi-colon on previous statement to silence");
}
TEST_CASE_FIXTURE(Fixture, "MultilineBlockSemicolonsWhitelisted")
{
LintResult result = lint(R"(
print(1); print(2); print(3)
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "MultilineBlockMissedSemicolon")
{
LintResult result = lint(R"(
print(1); print(2) print(3)
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "A new statement is on the same line; add semi-colon on previous statement to silence");
}
TEST_CASE_FIXTURE(Fixture, "MultilineBlockLocalDo")
{
LintResult result = lint(R"(
local _x do
_x = 5
end
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "ConfusingIndentation")
{
LintResult result = lint(R"(
print(math.max(1,
2))
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Statement spans multiple lines; use indentation to silence");
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal")
{
LintResult result = lint(R"(
function bar()
foo = 6
return foo
end
return bar()
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Global 'foo' is only used in the enclosing function 'bar'; consider changing it to local");
}
2022-03-04 16:36:33 +00:00
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFx")
{
LintResult result = lint(R"(
function bar()
foo = 6
return foo
end
function baz()
foo = 6
return foo
end
return bar() + baz()
)");
REQUIRE(1 == result.warnings.size());
2022-03-04 16:36:33 +00:00
CHECK_EQ(result.warnings[0].text, "Global 'foo' is never read before being written. Consider changing it to local");
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMultiFxWithRead")
{
LintResult result = lint(R"(
function bar()
foo = 6
return foo
end
function baz()
foo = 6
return foo
end
function read()
print(foo)
end
return bar() + baz() + read()
)");
REQUIRE(0 == result.warnings.size());
2022-03-04 16:36:33 +00:00
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalWithConditional")
{
LintResult result = lint(R"(
function bar()
if true then foo = 6 end
return foo
end
function baz()
foo = 6
return foo
end
return bar() + baz()
)");
REQUIRE(0 == result.warnings.size());
2022-03-04 16:36:33 +00:00
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocal3WithConditionalRead")
{
LintResult result = lint(R"(
function bar()
foo = 6
return foo
end
function baz()
foo = 6
return foo
end
function read()
if false then print(foo) end
end
return bar() + baz() + read()
)");
REQUIRE(0 == result.warnings.size());
2022-03-04 16:36:33 +00:00
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalInnerRead")
{
LintResult result = lint(R"(
function foo()
local f = function() return bar end
f()
bar = 42
end
function baz() bar = 0 end
return foo() + baz()
)");
REQUIRE(0 == result.warnings.size());
2022-03-04 16:36:33 +00:00
}
TEST_CASE_FIXTURE(Fixture, "GlobalAsLocalMulti")
{
LintResult result = lint(R"(
local createFunction = function(configValue)
-- Create an internal convenience function
local function internalLogic()
print(configValue) -- prints passed-in value
end
-- Here, we thought we were creating another internal convenience function
-- that closed over the passed-in configValue, but this is actually being
-- declared at module scope!
function moreInternalLogic()
print(configValue) -- nil!!!
end
return function()
internalLogic()
moreInternalLogic()
return nil
end
end
fnA = createFunction(true)
fnB = createFunction(false)
fnA() -- prints "true", "nil"
fnB() -- prints "false", "nil"
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(
result.warnings[0].text, "Global 'moreInternalLogic' is only used in the enclosing function defined at line 2; consider changing it to local"
);
}
TEST_CASE_FIXTURE(Fixture, "LocalShadowLocal")
{
LintResult result = lint(R"(
local arg = 6
print(arg)
local arg = 5
print(arg)
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'arg' shadows previous declaration at line 2");
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "LocalShadowGlobal")
{
LintResult result = lint(R"(
local math = math
global = math
function bar()
local global = math.max(5, 1)
return global
end
return bar()
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'global' shadows a global variable used at line 3");
}
TEST_CASE_FIXTURE(Fixture, "LocalShadowArgument")
{
LintResult result = lint(R"(
function bar(a, b)
local a = b + 1
return a
end
return bar()
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'a' shadows previous declaration at line 2");
}
TEST_CASE_FIXTURE(Fixture, "LocalUnused")
{
LintResult result = lint(R"(
local arg = 6
local function bar()
local arg = 5
local blarg = 6
if arg then
blarg = 42
end
end
return bar()
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'arg' is never used; prefix with '_' to silence");
CHECK_EQ(result.warnings[1].text, "Variable 'blarg' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "ImportUnused")
{
// Normally this would be defined externally, so hack it in for testing
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
LintResult result = lint(R"(
local Roact = require(game.Packages.Roact)
local _Roact = require(game.Packages.Roact)
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Import 'Roact' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "FunctionUnused")
{
LintResult result = lint(R"(
function bar()
end
local function qux()
end
function foo()
end
local function _unusedl()
end
function _unusedg()
end
return foo()
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Function 'bar' is never used; prefix with '_' to silence");
CHECK_EQ(result.warnings[1].text, "Function 'qux' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeBasic")
{
LintResult result = lint(R"(
do
return 'ok'
end
print("hi!")
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 5);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always returns)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeLoopBreak")
{
LintResult result = lint(R"(
while true do
do break end
print("nope")
end
print("hi!")
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 3);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always breaks)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeLoopContinue")
{
LintResult result = lint(R"(
while true do
do continue end
print("nope")
end
print("hi!")
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 3);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always continues)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeIfMerge")
{
LintResult result = lint(R"(
function foo1(a)
if a then
return 'x'
else
return 'y'
end
return 'z'
end
function foo2(a)
if a then
return 'x'
end
return 'z'
end
function foo3(a)
if a then
return 'x'
else
print('y')
end
return 'z'
end
return { foo1, foo2, foo3 }
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 7);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always returns)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeErrorReturnSilent")
{
LintResult result = lint(R"(
function foo1(a)
if a then
error('x')
return 'z'
else
error('y')
end
end
return foo1
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeAssertFalseReturnSilent")
{
LintResult result = lint(R"(
function foo1(a)
if a then
return 'z'
end
assert(false)
end
return foo1
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeErrorReturnNonSilentBranchy")
{
LintResult result = lint(R"(
function foo1(a)
if a then
error('x')
else
error('y')
end
return 'z'
end
return foo1
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 7);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always errors)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeErrorReturnPropagate")
{
LintResult result = lint(R"(
function foo1(a)
if a then
error('x')
return 'z'
else
error('y')
end
return 'x'
end
return foo1
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 8);
CHECK_EQ(result.warnings[0].text, "Unreachable code (previous statement always errors)");
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeLoopWhile")
{
LintResult result = lint(R"(
function foo1(a)
while a do
return 'z'
end
return 'x'
end
return foo1
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "UnreachableCodeLoopRepeat")
{
LintResult result = lint(R"(
function foo1(a)
repeat
return 'z'
until a
return 'x'
end
return foo1
)");
// this is technically a bug, since the repeat body always returns; fixing this bug is a bit more involved than I'd like
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "UnknownType")
{
unfreeze(frontend.globals.globalTypes);
TableType::Props instanceProps{
{"ClassName", {builtinTypes->anyType}},
};
TableType instanceTable{instanceProps, std::nullopt, frontend.globals.globalScope->level, Luau::TableState::Sealed};
TypeId instanceType = frontend.globals.globalTypes.addType(instanceTable);
TypeFun instanceTypeFun{{}, instanceType};
frontend.globals.globalScope->exportedTypeBindings["Part"] = instanceTypeFun;
2022-03-24 22:04:14 +00:00
LintResult result = lint(R"(
local game = ...
local _e01 = type(game) == "Part"
local _e02 = typeof(game) == "Bar"
local _e03 = typeof(game) == "vector"
2022-03-24 22:04:14 +00:00
local _o01 = type(game) == "number"
local _o02 = type(game) == "vector"
local _o03 = typeof(game) == "Part"
)");
REQUIRE(3 == result.warnings.size());
2022-03-24 22:04:14 +00:00
CHECK_EQ(result.warnings[0].location.begin.line, 2);
CHECK_EQ(result.warnings[0].text, "Unknown type 'Part' (expected primitive type)");
CHECK_EQ(result.warnings[1].location.begin.line, 3);
CHECK_EQ(result.warnings[1].text, "Unknown type 'Bar'");
CHECK_EQ(result.warnings[2].location.begin.line, 4);
CHECK_EQ(result.warnings[2].text, "Unknown type 'vector' (expected primitive or userdata type)");
}
TEST_CASE_FIXTURE(Fixture, "ForRangeTable")
{
LintResult result = lint(R"(
local t = {}
for i=#t,1 do
end
for i=#t,1,-1 do
end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 3);
CHECK_EQ(result.warnings[0].text, "For loop should iterate backwards; did you forget to specify -1 as step?");
}
TEST_CASE_FIXTURE(Fixture, "ForRangeBackwards")
{
LintResult result = lint(R"(
for i=8,1 do
end
for i=8,1,-1 do
end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[0].text, "For loop should iterate backwards; did you forget to specify -1 as step?");
}
TEST_CASE_FIXTURE(Fixture, "ForRangeImprecise")
{
LintResult result = lint(R"(
for i=1.3,7.5 do
end
for i=1.3,7.5,1 do
end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[0].text, "For loop ends at 7.3 instead of 7.5; did you forget to specify step?");
}
TEST_CASE_FIXTURE(Fixture, "ForRangeZero")
{
LintResult result = lint(R"(
for i=0,#t do
end
for i=(0),#t do -- to silence
end
for i=#t,0 do
end
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[0].text, "For loop starts at 0, but arrays start at 1");
CHECK_EQ(result.warnings[1].location.begin.line, 7);
CHECK_EQ(
result.warnings[1].text,
"For loop should iterate backwards; did you forget to specify -1 as step? Also consider changing 0 to 1 since arrays start at 1"
);
}
TEST_CASE_FIXTURE(Fixture, "UnbalancedAssignment")
{
LintResult result = lint(R"(
do
local _a,_b,_c = pcall()
end
do
local _a,_b,_c = pcall(), 5
end
do
local _a,_b,_c = pcall(), 5, 6
end
do
local _a,_b,_c = pcall(), 5, 6, 7
end
do
local _a,_b,_c = pcall(), nil
end
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line, 5);
CHECK_EQ(result.warnings[0].text, "Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence");
CHECK_EQ(result.warnings[1].location.begin.line, 11);
CHECK_EQ(result.warnings[1].text, "Assigning 4 values to 3 variables leaves some values unused");
}
TEST_CASE_FIXTURE(Fixture, "ImplicitReturn")
{
LintResult result = lint(R"(
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
--!nonstrict
function f1(a)
if not a then
return 5
end
end
function f2(a)
if not a then
return
end
end
function f3(a)
if not a then
return 5
else
return
end
end
function f4(a)
for i in pairs(a) do
if i > 5 then
return i
end
end
print("element not found")
end
function f5(a)
for i in pairs(a) do
if i > 5 then
return i
end
end
error("element not found")
end
f6 = function(a)
if a == 0 then
return 42
end
end
function f7(a)
repeat
return 10
until a ~= nil
end
return f1,f2,f3,f4,f5,f6,f7
)");
REQUIRE(3 == result.warnings.size());
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(result.warnings[0].location.begin.line, 5);
CHECK_EQ(
result.warnings[0].text,
"Function 'f1' can implicitly return no values even though there's an explicit return at line 5; add explicit return to silence"
);
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(result.warnings[1].location.begin.line, 29);
CHECK_EQ(
result.warnings[1].text,
"Function 'f4' can implicitly return no values even though there's an explicit return at line 26; add explicit return to silence"
);
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(result.warnings[2].location.begin.line, 45);
CHECK_EQ(
result.warnings[2].text,
"Function can implicitly return no values even though there's an explicit return at line 45; add explicit return to silence"
);
}
TEST_CASE_FIXTURE(Fixture, "ImplicitReturnInfiniteLoop")
{
LintResult result = lint(R"(
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
--!nonstrict
function f1(a)
while true do
if math.random() > 0.5 then
return 5
end
end
end
function f2(a)
repeat
if math.random() > 0.5 then
return 5
end
until false
end
function f3(a)
while true do
if math.random() > 0.5 then
return 5
end
if math.random() < 0.1 then
break
end
end
end
function f4(a)
repeat
if math.random() > 0.5 then
return 5
end
if math.random() < 0.1 then
break
end
until false
end
return f1,f2,f3,f4
)");
REQUIRE(2 == result.warnings.size());
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(result.warnings[0].location.begin.line, 26);
CHECK_EQ(
result.warnings[0].text,
"Function 'f3' can implicitly return no values even though there's an explicit return at line 22; add explicit return to silence"
);
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(result.warnings[1].location.begin.line, 37);
CHECK_EQ(
result.warnings[1].text,
"Function 'f4' can implicitly return no values even though there's an explicit return at line 33; add explicit return to silence"
);
}
TEST_CASE_FIXTURE(Fixture, "TypeAnnotationsShouldNotProduceWarnings")
{
LintResult result = lint(R"(--!strict
type InputData = {
id: number,
inputType: EnumItem,
inputState: EnumItem,
updated: number,
position: Vector3,
keyCode: EnumItem,
name: string
}
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "BreakFromInfiniteLoopMakesStatementReachable")
{
LintResult result = lint(R"(
local bar = ...
repeat
if bar then
break
end
return 2
until true
return 1
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "IgnoreLintAll")
{
LintResult result = lint(R"(
--!nolint
return foo
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "IgnoreLintSpecific")
{
LintResult result = lint(R"(
--!nolint UnknownGlobal
local x = 1
return foo
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringFormat")
{
LintResult result = lint(R"(
-- incorrect format strings
string.format("%")
string.format("%??d")
string.format("%Y")
-- incorrect format strings, self call
local _ = ("%"):format()
-- correct format strings, just to uh make sure
2022-02-04 16:45:57 +00:00
string.format("hello %+10d %.02f %%", 4, 5)
)");
REQUIRE(4 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid format string: unfinished format specifier");
CHECK_EQ(result.warnings[1].text, "Invalid format string: invalid format specifier: must be a string format specifier or %");
CHECK_EQ(result.warnings[2].text, "Invalid format string: invalid format specifier: must be a string format specifier or %");
CHECK_EQ(result.warnings[3].text, "Invalid format string: unfinished format specifier");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringPack")
{
LintResult result = lint(R"(
-- incorrect pack specifiers
string.pack("?")
string.packsize("?")
string.unpack("?")
-- missing size
string.packsize("bc")
-- incorrect X alignment
string.packsize("X")
string.packsize("X i")
-- correct X alignment
string.packsize("Xi")
-- packsize can't be used with variable sized formats
string.packsize("s")
-- out of range size specifiers
string.packsize("i0")
string.packsize("i17")
-- a very very very out of range size specifier
string.packsize("i99999999999999999999")
string.packsize("c99999999999999999999")
-- correct format specifiers
string.packsize("=!1bbbI3c42")
)");
REQUIRE(11 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
CHECK_EQ(result.warnings[1].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
CHECK_EQ(result.warnings[2].text, "Invalid pack format: unexpected character; must be a pack specifier or space");
CHECK_EQ(result.warnings[3].text, "Invalid pack format: fixed-sized string format must specify the size");
CHECK_EQ(result.warnings[4].text, "Invalid pack format: X must be followed by a size specifier");
CHECK_EQ(result.warnings[5].text, "Invalid pack format: X must be followed by a size specifier");
CHECK_EQ(result.warnings[6].text, "Invalid pack format: pack specifier must be fixed-size");
CHECK_EQ(result.warnings[7].text, "Invalid pack format: integer size must be in range [1,16]");
CHECK_EQ(result.warnings[8].text, "Invalid pack format: integer size must be in range [1,16]");
CHECK_EQ(result.warnings[9].text, "Invalid pack format: size specifier is too large");
CHECK_EQ(result.warnings[10].text, "Invalid pack format: size specifier is too large");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringMatch")
{
LintResult result = lint(R"(
local s = ...
-- incorrect character class specifiers
string.match(s, "%q")
string.gmatch(s, "%q")
string.find(s, "%q")
string.gsub(s, "%q", "")
-- various errors
string.match(s, "%")
string.match(s, "[%1]")
string.match(s, "%0")
string.match(s, "(%d)%2")
string.match(s, "%bx")
string.match(s, "%foo")
string.match(s, '(%d))')
string.match(s, '(%d')
string.match(s, '[%d')
string.match(s, '%,')
-- self call - not detected because we don't know the type!
local _ = s:match("%q")
-- correct patterns
string.match(s, "[A-Z]+(%d)%1")
)");
REQUIRE(14 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[2].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[3].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[4].text, "Invalid match pattern: unfinished character class");
CHECK_EQ(result.warnings[5].text, "Invalid match pattern: sets can not contain capture references");
CHECK_EQ(result.warnings[6].text, "Invalid match pattern: invalid capture reference, must be 1-9");
CHECK_EQ(result.warnings[7].text, "Invalid match pattern: invalid capture reference, must refer to a valid capture");
CHECK_EQ(result.warnings[8].text, "Invalid match pattern: missing brace characters for balanced match");
CHECK_EQ(result.warnings[9].text, "Invalid match pattern: missing set after a frontier pattern");
CHECK_EQ(result.warnings[10].text, "Invalid match pattern: unexpected ) without a matching (");
CHECK_EQ(result.warnings[11].text, "Invalid match pattern: expected ) at the end of the string to close a capture");
CHECK_EQ(result.warnings[12].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[13].text, "Invalid match pattern: expected a magic character after %");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringMatchNested")
{
LintResult result = lint(R"~(
local s = ...
-- correct reference to nested pattern
string.match(s, "((a)%2)")
-- incorrect reference to nested pattern (not closed yet)
string.match(s, "((a)%1)")
-- incorrect reference to nested pattern (index out of range)
string.match(s, "((a)%3)")
)~");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid capture reference, must refer to a closed capture");
CHECK_EQ(result.warnings[0].location.begin.line, 7);
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid capture reference, must refer to a valid capture");
CHECK_EQ(result.warnings[1].location.begin.line, 10);
}
TEST_CASE_FIXTURE(Fixture, "FormatStringMatchSets")
{
LintResult result = lint(R"~(
local s = ...
-- fake empty sets (but actually sets that aren't closed)
string.match(s, "[]")
string.match(s, "[^]")
-- character ranges in sets
string.match(s, "[%a-b]")
string.match(s, "[a-%b]")
-- invalid escapes
string.match(s, "[%q]")
string.match(s, "[%;]")
-- capture refs in sets
string.match(s, "[%1]")
-- valid escapes and - at the end
string.match(s, "[%]x-]")
-- % escapes itself
string.match(s, "[%%]")
-- this abomination is a valid pattern due to rules wrt handling empty sets
string.match(s, "[]|'[]")
string.match(s, "[^]|'[]")
)~");
REQUIRE(7 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[2].text, "Invalid match pattern: character range can't include character sets");
CHECK_EQ(result.warnings[3].text, "Invalid match pattern: character range can't include character sets");
CHECK_EQ(result.warnings[4].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[5].text, "Invalid match pattern: expected a magic character after %");
CHECK_EQ(result.warnings[6].text, "Invalid match pattern: sets can not contain capture references");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringFindArgs")
{
LintResult result = lint(R"(
local s = ...
-- incorrect character class specifier
string.find(s, "%q")
-- raw string find
string.find(s, "%q", 1, true)
string.find(s, "%q", 1, math.random() < 0.5)
-- incorrect character class specifier
string.find(s, "%q", 1, false)
-- missing arguments
string.find()
string.find("foo");
("foo"):find()
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[0].location.begin.line, 4);
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: invalid character class, must refer to a defined class or its inverse");
CHECK_EQ(result.warnings[1].location.begin.line, 11);
}
TEST_CASE_FIXTURE(Fixture, "FormatStringReplace")
{
LintResult result = lint(R"(
local s = ...
-- incorrect replacements
string.gsub(s, '(%d+)', "%")
string.gsub(s, '(%d+)', "%x")
string.gsub(s, '(%d+)', "%2")
string.gsub(s, '', "%1")
-- correct replacements
string.gsub(s, '[A-Z]+(%d)', "%0%1")
string.gsub(s, 'foo', "%0")
)");
REQUIRE(4 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match replacement: unfinished replacement");
CHECK_EQ(result.warnings[1].text, "Invalid match replacement: unexpected replacement character; must be a digit or %");
CHECK_EQ(result.warnings[2].text, "Invalid match replacement: invalid capture index, must refer to pattern capture");
CHECK_EQ(result.warnings[3].text, "Invalid match replacement: invalid capture index, must refer to pattern capture");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringDate")
{
LintResult result = lint(R"(
-- incorrect formats
os.date("%")
os.date("%L")
os.date("%?")
2022-02-04 16:45:57 +00:00
os.date("\0")
-- correct formats
os.date("it's %c now")
os.date("!*t")
)");
REQUIRE(4 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid date format: unfinished replacement");
CHECK_EQ(result.warnings[1].text, "Invalid date format: unexpected replacement character; must be a date format specifier or %");
CHECK_EQ(result.warnings[2].text, "Invalid date format: unexpected replacement character; must be a date format specifier or %");
2022-02-04 16:45:57 +00:00
CHECK_EQ(result.warnings[3].text, "Invalid date format: date format can not contain null characters");
}
TEST_CASE_FIXTURE(Fixture, "FormatStringTyped")
{
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
LintResult result = lint(R"~(
local s: string, nons = ...
string.match(s, "[]")
s:match("[]")
-- no warning here since we don't know that it's a string
nons:match("[]")
)~");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[0].location.begin.line, 3);
CHECK_EQ(result.warnings[1].text, "Invalid match pattern: expected ] at the end of the string to close a set");
CHECK_EQ(result.warnings[1].location.begin.line, 4);
}
TEST_CASE_FIXTURE(Fixture, "TableLiteral")
{
LintResult result = lint(R"(-- line 1
_ = {
first = 1,
second = 2,
first = 3,
}
_ = {
first = 1,
["first"] = 2,
}
_ = {
1, 2, 3,
[1] = 42
}
_ = {
[3] = 42,
1, 2, 3,
}
local _: {
first: number,
second: string,
first: boolean
}
_ = {
1, 2, 3,
[0] = 42,
[4] = 42,
}
_ = {
[1] = 1,
[2] = 2,
[1] = 3,
}
)");
REQUIRE(6 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Table field 'first' is a duplicate; previously defined at line 3");
CHECK_EQ(result.warnings[1].text, "Table field 'first' is a duplicate; previously defined at line 9");
CHECK_EQ(result.warnings[2].text, "Table index 1 is a duplicate; previously defined as a list entry");
CHECK_EQ(result.warnings[3].text, "Table index 3 is a duplicate; previously defined as a list entry");
CHECK_EQ(result.warnings[4].text, "Table type field 'first' is a duplicate; previously defined at line 24");
CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36");
}
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(Fixture, "read_write_table_props")
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
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
LintResult result = lint(R"(-- line 1
type A = {x: number}
type B = {read x: number, write x: number}
type C = {x: number, read x: number} -- line 4
type D = {x: number, write x: number}
type E = {read x: number, x: boolean}
type F = {read x: number, read x: number}
type G = {write x: number, x: boolean}
type H = {write x: number, write x: boolean}
)");
REQUIRE(6 == result.warnings.size());
CHECK(result.warnings[0].text == "Table type field 'x' is already read-write; previously defined at line 4");
CHECK(result.warnings[1].text == "Table type field 'x' is already read-write; previously defined at line 5");
CHECK(result.warnings[2].text == "Table type field 'x' already has a read type defined at line 6");
CHECK(result.warnings[3].text == "Table type field 'x' is a duplicate; previously defined at line 7");
CHECK(result.warnings[4].text == "Table type field 'x' already has a write type defined at line 8");
CHECK(result.warnings[5].text == "Table type field 'x' is a duplicate; previously defined at line 9");
}
TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
{
LintResult result = lint(R"(
local Foo = require(script.Parent.Foo)
local x: Foo.Y = 1
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence");
}
TEST_CASE_FIXTURE(Fixture, "DisableUnknownGlobalWithTypeChecking")
{
LintResult result = lint(R"(
--!strict
unknownGlobal()
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "no_spurious_warning_after_a_function_type_alias")
{
LintResult result = lint(R"(
local exports = {}
export type PathFunction<P> = (P?) -> string
exports.tokensToFunction = function() end
return exports
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "use_all_parent_scopes_for_globals")
{
ScopePtr testScope = frontend.addEnvironment("Test");
unfreeze(frontend.globals.globalTypes);
frontend.loadDefinitionFile(
frontend.globals,
testScope,
R"(
declare Foo: number
)",
"@test",
/* captureComments */ false
);
freeze(frontend.globals.globalTypes);
fileResolver.environments["A"] = "Test";
fileResolver.source["A"] = R"(
local _foo: Foo = 123
-- os.clock comes from the global scope, the parent of this module's environment
local _bar: typeof(os.clock) = os.clock
)";
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
LintResult result = lintModule("A");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "DeadLocalsUsed")
{
LintResult result = lint(R"(
--!nolint LocalShadow
do
local x
for x in pairs({}) do
print(x)
end
print(x) -- x is not initialized
end
do
local a, b, c = 1, 2
print(a, b, c) -- c is not initialized
end
do
local a, b, c = table.unpack({})
print(a, b, c) -- no warning as we don't know anything about c
end
)");
REQUIRE(3 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Variable 'x' defined at line 4 is never initialized or assigned; initialize with 'nil' to silence");
CHECK_EQ(result.warnings[1].text, "Assigning 2 values to 3 variables initializes extra variables with nil; add 'nil' to value list to silence");
CHECK_EQ(result.warnings[2].text, "Variable 'c' defined at line 12 is never initialized or assigned; initialize with 'nil' to silence");
}
TEST_CASE_FIXTURE(Fixture, "LocalFunctionNotDead")
{
LintResult result = lint(R"(
local foo
function foo() end
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "DuplicateGlobalFunction")
{
LintResult result = lint(R"(
function x() end
function x() end
return x
)");
REQUIRE_EQ(1, result.warnings.size());
const auto& w = result.warnings[0];
CHECK_EQ(LintWarning::Code_DuplicateFunction, w.code);
CHECK_EQ("Duplicate function definition: 'x' also defined on line 2", w.text);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateLocalFunction")
{
LintOptions options;
options.setDefaults();
options.enableWarning(LintWarning::Code_DuplicateFunction);
options.enableWarning(LintWarning::Code_LocalShadow);
LintResult result = lint(
R"(
local function x() end
print(x)
local function x() end
return x
)",
options
);
REQUIRE_EQ(1, result.warnings.size());
CHECK_EQ(LintWarning::Code_DuplicateFunction, result.warnings[0].code);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateMethod")
{
LintResult result = lint(R"(
local T = {}
function T:x() end
function T:x() end
return x
)");
REQUIRE_EQ(1, result.warnings.size());
const auto& w = result.warnings[0];
CHECK_EQ(LintWarning::Code_DuplicateFunction, w.code);
CHECK_EQ("Duplicate function definition: 'T.x' also defined on line 3", w.text);
}
TEST_CASE_FIXTURE(Fixture, "DontTriggerTheWarningIfTheFunctionsAreInDifferentScopes")
{
LintResult result = lint(R"(
if true then
function c() end
else
function c() end
end
return c
)");
REQUIRE(0 == result.warnings.size());
}
TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF")
{
LintResult result = lint(R"(
local Hooty = require(workspace.A)
local HoHooty = require(workspace.A)
local h: Hooty.Pointy = ruire(workspace.A)
local h: H
local h: Hooty.Pointy = ruire(workspace.A)
local hh: Hooty.Pointy = ruire(workspace.A)
local h: Hooty.Pointy = ruire(workspace.A)
linooty.Pointy = ruire(workspace.A)
local hh: Hooty.Pointy = ruire(workspace.A)
local h: Hooty.Pointy = ruire(workspace.A)
linty = ruire(workspace.A)
local h: Hooty.Pointy = ruire(workspace.A)
local hh: Hooty.Pointy = ruire(workspace.A)
local h: Hooty.Pointy = ruire(workspace.A)
local h: Hooty.Pt
)");
REQUIRE(12 == result.warnings.size());
}
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped")
{
unfreeze(frontend.globals.globalTypes);
TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}});
2022-04-15 00:57:43 +01:00
persist(instanceType);
frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType};
getMutable<ClassType>(instanceType)->props = {
{"Name", {builtinTypes->stringType}},
{"DataCost", {builtinTypes->numberType, /* deprecated= */ true}},
{"Wait", {builtinTypes->anyType, /* deprecated= */ true}},
};
2022-02-11 19:02:09 +00:00
TypeId colorType =
frontend.globals.globalTypes.addType(TableType{{}, std::nullopt, frontend.globals.globalScope->level, Luau::TableState::Sealed});
2022-02-11 19:02:09 +00:00
getMutable<TableType>(colorType)->props = {{"toHSV", {builtinTypes->anyType, /* deprecated= */ true, "Color3:ToHSV"}}};
2022-02-11 19:02:09 +00:00
addGlobalBinding(frontend.globals, "Color3", Binding{colorType, {}});
2022-02-18 01:18:01 +00:00
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend.globals, "table")))
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
{
ttv->props["foreach"].deprecated = true;
ttv->props["getn"].deprecated = true;
ttv->props["getn"].deprecatedSuggestion = "#";
}
freeze(frontend.globals.globalTypes);
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
LintResult result = lint(R"(
return function (i: Instance)
i:Wait(1.0)
print(i.Name)
2022-02-11 19:02:09 +00:00
print(Color3.toHSV())
print(Color3.doesntexist, i.doesntexist) -- type error, but this verifies we correctly handle non-existent members
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
print(table.getn({}))
table.foreach({}, function() end)
print(table.nogetn()) -- verify that we correctly handle non-existent members
return i.DataCost
end
)");
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
REQUIRE(5 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Member 'Instance.Wait' is deprecated");
2022-02-11 19:02:09 +00:00
CHECK_EQ(result.warnings[1].text, "Member 'toHSV' is deprecated, use 'Color3:ToHSV' instead");
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
CHECK_EQ(result.warnings[2].text, "Member 'table.getn' is deprecated, use '#' instead");
CHECK_EQ(result.warnings[3].text, "Member 'table.foreach' is deprecated");
CHECK_EQ(result.warnings[4].text, "Member 'Instance.DataCost' is deprecated");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiUntyped")
{
if (TableType* ttv = getMutable<TableType>(getGlobalBinding(frontend.globals, "table")))
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
{
ttv->props["foreach"].deprecated = true;
ttv->props["getn"].deprecated = true;
ttv->props["getn"].deprecatedSuggestion = "#";
}
LintResult result = lint(R"(
-- TODO
Sync to upstream/release/566 (#853) * Fixed incorrect lexeme generated for string parts in the middle of an interpolated string (Fixes https://github.com/Roblox/luau/issues/744) * DeprecatedApi lint can report some issues without type inference information * Fixed performance of autocomplete requests when suggestions have large intersection types (Solves https://github.com/Roblox/luau/discussions/847) * Marked `table.getn`/`foreach`/`foreachi` as deprecated ([RFC: Deprecate table.getn/foreach/foreachi](https://github.com/Roblox/luau/blob/master/rfcs/deprecate-table-getn-foreach.md)) * With -O2 optimization level, we now optimize builtin calls based on known argument/return count. Note that this change can be observable if `getfenv/setfenv` is used to substitute a builtin, especially if arity is different. Fastcall heavy tests show a 1-2% improvement. * Luau can now be built with clang-cl (Fixes https://github.com/Roblox/luau/issues/736) We also made many improvements to our experimental components. For our new type solver: * Overhauled data flow analysis system, fixed issues with 'repeat' loops, global variables and type annotations * Type refinements now work on generic table indexing with a string literal * Type refinements will properly track potentially 'nil' values (like t[x] for a missing key) and their further refinements * Internal top table type is now isomorphic to `{}` which fixes issues when `typeof(v) == 'table'` type refinement is handled * References to non-existent types in type annotations no longer resolve to 'error' type like in old solver * Improved handling of class unions in property access expressions * Fixed default type packs * Unsealed tables can now have metatables * Restored expected types for function arguments And for native code generation: * Added min and max IR instructions mapping to vminsd/vmaxsd on x64 * We now speculatively extract direct execution fast-paths based on expected types of expressions which provides better optimization opportunities inside a single basic block * Translated existing math fastcalls to IR form to improve tag guard removal and constant propagation
2023-03-03 20:21:14 +00:00
return function ()
print(table.getn({}))
table.foreach({}, function() end)
print(table.nogetn()) -- verify that we correctly handle non-existent members
end
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Member 'table.getn' is deprecated, use '#' instead");
CHECK_EQ(result.warnings[1].text, "Member 'table.foreach' is deprecated");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiFenv")
{
LintResult result = lint(R"(
local f, g, h = ...
getfenv(1)
getfenv(f :: () -> ())
getfenv(g :: number)
getfenv(h :: any)
setfenv(1, {})
setfenv(f :: () -> (), {})
setfenv(g :: number, {})
setfenv(h :: any, {})
)");
REQUIRE(4 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Function 'getfenv' is deprecated; consider using 'debug.info' instead");
CHECK_EQ(result.warnings[0].location.begin.line + 1, 4);
CHECK_EQ(result.warnings[1].text, "Function 'getfenv' is deprecated; consider using 'debug.info' instead");
CHECK_EQ(result.warnings[1].location.begin.line + 1, 6);
CHECK_EQ(result.warnings[2].text, "Function 'setfenv' is deprecated");
CHECK_EQ(result.warnings[2].location.begin.line + 1, 9);
CHECK_EQ(result.warnings[3].text, "Function 'setfenv' is deprecated");
CHECK_EQ(result.warnings[3].location.begin.line + 1, 11);
}
2022-05-13 20:36:37 +01:00
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{
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
LintResult result = lint(R"(
local t = {}
local tt = {}
table.insert(t, #t, 42)
table.insert(t, (#t), 42) -- silenced
table.insert(t, #t + 1, 42)
table.insert(t, #tt + 1, 42) -- different table, ok
table.insert(t, 0, 42)
table.remove(t, 0)
table.remove(t, #t-1)
table.insert(t, string.find("hello", "h"))
table.move(t, 0, #t, 1, tt)
table.move(t, 1, #t, 0, tt)
table.create(42, {})
table.create(42, {} :: {})
)");
REQUIRE(10 == result.warnings.size());
CHECK_EQ(
result.warnings[0].text,
"table.insert will insert the value before the last element, which is likely a bug; consider removing the "
"second argument or wrap it in parentheses to silence"
);
CHECK_EQ(result.warnings[1].text, "table.insert will append the value to the table; consider removing the second argument for efficiency");
CHECK_EQ(result.warnings[2].text, "table.insert uses index 0 but arrays are 1-based; did you mean 1 instead?");
CHECK_EQ(result.warnings[3].text, "table.remove uses index 0 but arrays are 1-based; did you mean 1 instead?");
CHECK_EQ(
result.warnings[4].text,
"table.remove will remove the value before the last element, which is likely a bug; consider removing the "
"second argument or wrap it in parentheses to silence"
);
CHECK_EQ(
result.warnings[5].text,
"table.insert may change behavior if the call returns more than one result; consider adding parentheses around second argument"
);
CHECK_EQ(result.warnings[6].text, "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?");
CHECK_EQ(result.warnings[7].text, "table.move uses index 0 but arrays are 1-based; did you mean 1 instead?");
2022-02-04 16:45:57 +00:00
CHECK_EQ(
result.warnings[8].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"
);
2022-02-04 16:45:57 +00:00
CHECK_EQ(
result.warnings[9].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead"
);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer")
{
// CLI-116824 Linter incorrectly issues false positive when taking the length of a unannotated string function argument
if (FFlag::LuauSolverV2)
return;
LintResult result = lint(R"(
local t1 = {} -- ok: empty
local t2 = {1, 2} -- ok: array
local t3 = { a = 1, b = 2 } -- not ok: dictionary
local t4: {[number]: number} = {} -- ok: array
local t5: {[string]: number} = {} -- not ok: dictionary
local t6: typeof(setmetatable({1, 2}, {})) = {} -- ok: table with metatable
local t7: string = "hello" -- ok: string
local t8: {number} | {n: number} = {} -- ok: union
-- not ok
print(#t3)
print(#t5)
ipairs(t5)
-- disabled
-- ipairs(t3) adds indexer to t3, silencing error on #t3
-- ok
print(#t1)
print(#t2)
print(#t4)
print(#t6)
print(#t7)
print(#t8)
ipairs(t1)
ipairs(t2)
ipairs(t4)
ipairs(t6)
ipairs(t7)
ipairs(t8)
-- ok, subtle: text is a string here implicitly, but the type annotation isn't available
-- type checker assigns a type of generic table with the 'sub' member; we don't emit warnings on generic tables
-- to avoid generating a false positive here
function _impliedstring(element, text)
for i = 1, #text do
element:sendText(text:sub(i, i))
end
end
)");
REQUIRE(3 == result.warnings.size());
CHECK_EQ(result.warnings[0].location.begin.line + 1, 12);
CHECK_EQ(result.warnings[0].text, "Using '#' on a table without an array part is likely a bug");
CHECK_EQ(result.warnings[1].location.begin.line + 1, 13);
CHECK_EQ(result.warnings[1].text, "Using '#' on a table with string keys is likely a bug");
CHECK_EQ(result.warnings[2].location.begin.line + 1, 14);
CHECK_EQ(result.warnings[2].text, "Using 'ipairs' on a table with string keys is likely a bug");
}
TEST_CASE_FIXTURE(Fixture, "DuplicateConditions")
{
LintResult result = lint(R"(
if true then
elseif false then
elseif true then -- duplicate
end
if true then
elseif false then
else
if true then -- duplicate
end
end
_ = true and true
_ = true or true
_ = (true and false) and true
_ = (true and true) and true
_ = (true and true) or true
_ = (true and false) and (42 and false)
_ = true and true or false -- no warning since this is is a common pattern used as a ternary replacement
2022-02-11 19:02:09 +00:00
_ = if true then 1 elseif true then 2 else 3
)");
REQUIRE(8 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
CHECK_EQ(result.warnings[0].location.begin.line + 1, 4);
CHECK_EQ(result.warnings[1].text, "Condition has already been checked on column 5");
CHECK_EQ(result.warnings[2].text, "Condition has already been checked on column 5");
CHECK_EQ(result.warnings[3].text, "Condition has already been checked on column 6");
CHECK_EQ(result.warnings[4].text, "Condition has already been checked on column 6");
CHECK_EQ(result.warnings[5].text, "Condition has already been checked on column 6");
CHECK_EQ(result.warnings[6].text, "Condition has already been checked on column 15");
CHECK_EQ(result.warnings[6].location.begin.line + 1, 19);
2022-02-11 19:02:09 +00:00
CHECK_EQ(result.warnings[7].text, "Condition has already been checked on column 8");
}
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsExpr")
{
LintResult result = lint(R"(
local correct, opaque = ...
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
if correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", `string {opaque}`)}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", `string {opaque}`)}) then
elseif correct({a = 1, b = 2 * (-2), c = opaque.path['with']("calls", false)}) then
end
)");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 4");
CHECK_EQ(result.warnings[0].location.begin.line + 1, 5);
}
TEST_CASE_FIXTURE(Fixture, "DuplicateLocal")
{
LintResult result = lint(R"(
function foo(a1, a2, a3, a1)
end
local _, _, _ = ... -- ok!
local a1, a2, a1 = ... -- not ok
local moo = {}
function moo:bar(self)
end
return foo, moo, a1, a2
)");
REQUIRE(4 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Function parameter 'a1' already defined on column 14");
CHECK_EQ(result.warnings[1].text, "Variable 'a1' is never used; prefix with '_' to silence");
CHECK_EQ(result.warnings[2].text, "Variable 'a1' already defined on column 7");
CHECK_EQ(result.warnings[3].text, "Function parameter 'self' already defined implicitly");
}
2022-02-11 19:02:09 +00:00
TEST_CASE_FIXTURE(Fixture, "MisleadingAndOr")
{
LintResult result = lint(R"(
_ = math.random() < 0.5 and true or 42
_ = math.random() < 0.5 and false or 42 -- misleading
_ = math.random() < 0.5 and nil or 42 -- misleading
_ = math.random() < 0.5 and 0 or 42
_ = (math.random() < 0.5 and false) or 42 -- currently ignored
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(
result.warnings[0].text,
"The and-or expression always evaluates to the second alternative because the first alternative is false; "
"consider using if-then-else expression instead"
);
CHECK_EQ(
result.warnings[1].text,
"The and-or expression always evaluates to the second alternative because the first alternative is nil; "
"consider using if-then-else expression instead"
);
2022-02-18 01:18:01 +00:00
}
TEST_CASE_FIXTURE(Fixture, "WrongComment")
{
LintResult result = lint(R"(
--!strict
--!struct
--!nolintGlobal
--!nolint Global
--!nolint KnownGlobal
--!nolint UnknownGlobal
--! no more lint
--!strict here
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 16:13:53 +01:00
--!native on
2022-02-18 01:18:01 +00:00
do end
--!nolint
)");
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 16:13:53 +01:00
REQUIRE(7 == result.warnings.size());
2022-02-18 01:18:01 +00:00
CHECK_EQ(result.warnings[0].text, "Unknown comment directive 'struct'; did you mean 'strict'?");
CHECK_EQ(result.warnings[1].text, "Unknown comment directive 'nolintGlobal'");
CHECK_EQ(result.warnings[2].text, "nolint directive refers to unknown lint rule 'Global'");
CHECK_EQ(result.warnings[3].text, "nolint directive refers to unknown lint rule 'KnownGlobal'; did you mean 'UnknownGlobal'?");
CHECK_EQ(result.warnings[4].text, "Comment directive with the type checking mode has extra symbols at the end of the line");
Sync to upstream/release/588 (#992) Type checker/autocomplete: * `Luau::autocomplete` no longer performs typechecking internally, make sure to run `Frontend::check` before performing autocomplete requests * Autocomplete string suggestions without "" are now only suggested inside the "" * Autocomplete suggestions now include `function (anonymous autofilled)` key with a full suggestion for the function expression (with arguments included) stored in `AutocompleteEntry::insertText` * `AutocompleteEntry::indexedWithSelf` is provided for function call suggestions made with `:` * Cyclic modules now see each other type exports as `any` to prevent memory use-after-free (similar to module return type) Runtime: * Updated inline/loop unroll cost model to better handle assignments (Fixes https://github.com/Roblox/luau/issues/978) * `math.noise` speed was improved by ~30% * `table.concat` speed was improved by ~5-7% * `tonumber` and `tostring` now have fastcall paths that execute ~1.5x and ~2.5x faster respectively (fixes #777) * Fixed crash in `luaL_typename` when index refers to a non-existing value * Fixed potential out of memory scenario when using `string.sub` or `string.char` in a loop * Fixed behavior of some fastcall builtins when called without arguments under -O2 to match original functions * Support for native code execution in VM is now enabled by default (note: native code still has to be generated explicitly) * `Codegen::compile` now accepts `CodeGen_OnlyNativeModules` flag. When set, only modules that have a `--!native` hot-comment at the top will be compiled to native code In our new typechecker: * Generic type packs are no longer considered to be variadic during unification * Timeout and cancellation now works in new solver * Fixed false positive errors around 'table' and 'function' type refinements * Table literals now use covariant unification rules. This is sound since literal has no type specified and has no aliases * Fixed issues with blocked types escaping the constraint solver * Fixed more places where error messages that should've been suppressed were still reported * Fixed errors when iterating over a top table type In our native code generation (jit): * 'DebugLuauAbortingChecks' flag is now supported on A64 * LOP_NEWCLOSURE has been translated to IR
2023-07-28 16:13:53 +01:00
CHECK_EQ(result.warnings[5].text, "native directive has extra symbols at the end of the line");
CHECK_EQ(result.warnings[6].text, "Comment directive is ignored because it is placed after the first non-comment token");
2022-02-18 01:18:01 +00:00
}
TEST_CASE_FIXTURE(Fixture, "WrongCommentMuteSelf")
{
LintResult result = lint(R"(
--!nolint
--!struct
)");
REQUIRE(0 == result.warnings.size()); // --!nolint disables WrongComment lint :)
2022-02-11 19:02:09 +00:00
}
2022-02-24 23:53:37 +00:00
TEST_CASE_FIXTURE(Fixture, "DuplicateConditionsIfStatAndExpr")
{
LintResult result = lint(R"(
if if 1 then 2 else 3 then
elseif if 1 then 2 else 3 then
elseif if 0 then 5 else 4 then
end
)");
REQUIRE(1 == result.warnings.size());
2022-02-24 23:53:37 +00:00
CHECK_EQ(result.warnings[0].text, "Condition has already been checked on line 2");
}
2022-07-14 23:52:26 +01:00
TEST_CASE_FIXTURE(Fixture, "WrongCommentOptimize")
{
LintResult result = lint(R"(
--!optimize
--!optimize me
--!optimize 100500
--!optimize 2
)");
REQUIRE(3 == result.warnings.size());
2022-07-14 23:52:26 +01:00
CHECK_EQ(result.warnings[0].text, "optimize directive requires an optimization level");
CHECK_EQ(result.warnings[1].text, "optimize directive uses unknown optimization level 'me', 0..2 expected");
CHECK_EQ(result.warnings[2].text, "optimize directive uses unknown optimization level '100500', 0..2 expected");
result = lint("--!optimize ");
REQUIRE(1 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "optimize directive requires an optimization level");
}
TEST_CASE_FIXTURE(Fixture, "TestStringInterpolation")
{
LintResult result = lint(R"(
--!nocheck
local _ = `unknown {foo}`
)");
REQUIRE(1 == result.warnings.size());
2022-07-14 23:52:26 +01:00
}
TEST_CASE_FIXTURE(Fixture, "IntegerParsing")
2022-08-04 23:35:33 +01:00
{
LintResult result = lint(R"(
local _ = 0b10000000000000000000000000000000000000000000000000000000000000000
local _ = 0x10000000000000000
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Binary number literal exceeded available precision and was truncated to 2^64");
CHECK_EQ(result.warnings[1].text, "Hexadecimal number literal exceeded available precision and was truncated to 2^64");
}
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDecimalImprecise")
{
LintResult result = lint(R"(
local _ = 10000000000000000000000000000000000000000000000000000000000000000
local _ = 10000000000000001
local _ = -10000000000000001
-- 10^16 = 2^16 * 5^16, 5^16 only requires 38 bits
local _ = 10000000000000000
local _ = -10000000000000000
-- smallest possible number that is parsed imprecisely
local _ = 9007199254740993
local _ = -9007199254740993
-- note that numbers before and after parse precisely (number after is even => 1 more mantissa bit)
local _ = 9007199254740992
local _ = 9007199254740994
-- large powers of two should work as well (this is 2^63)
local _ = -9223372036854775808
)");
REQUIRE(5 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[1].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[1].location.begin.line, 2);
CHECK_EQ(result.warnings[2].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[2].location.begin.line, 3);
CHECK_EQ(result.warnings[3].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[3].location.begin.line, 10);
CHECK_EQ(result.warnings[4].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[4].location.begin.line, 11);
}
TEST_CASE_FIXTURE(Fixture, "IntegerParsingHexImprecise")
{
LintResult result = lint(R"(
local _ = 0x1234567812345678
-- smallest possible number that is parsed imprecisely
local _ = 0x20000000000001
-- note that numbers before and after parse precisely (number after is even => 1 more mantissa bit)
local _ = 0x20000000000000
local _ = 0x20000000000002
-- large powers of two should work as well (this is 2^63)
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
local _ = 0x80000000000000
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[0].location.begin.line, 1);
CHECK_EQ(result.warnings[1].text, "Number literal exceeded available precision and was truncated to closest representable number");
CHECK_EQ(result.warnings[1].location.begin.line, 4);
2022-08-04 23:35:33 +01:00
}
TEST_CASE_FIXTURE(Fixture, "ComparisonPrecedence")
{
LintResult result = lint(R"(
local a, b = ...
local _ = not a == b
local _ = not a ~= b
local _ = not a <= b
local _ = a <= b == 0
local _ = a <= b <= 0
local _ = not a == not b -- weird but ok
-- silence tests for all of the above
local _ = not (a == b)
local _ = (not a) == b
local _ = not (a ~= b)
local _ = (not a) ~= b
local _ = not (a <= b)
local _ = (not a) <= b
local _ = (a <= b) == 0
local _ = a <= (b == 0)
)");
REQUIRE(5 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "not X == Y is equivalent to (not X) == Y; consider using X ~= Y, or add parentheses to silence");
CHECK_EQ(result.warnings[1].text, "not X ~= Y is equivalent to (not X) ~= Y; consider using X == Y, or add parentheses to silence");
CHECK_EQ(result.warnings[2].text, "not X <= Y is equivalent to (not X) <= Y; add parentheses to silence");
CHECK_EQ(result.warnings[3].text, "X <= Y == Z is equivalent to (X <= Y) == Z; add parentheses to silence");
CHECK_EQ(result.warnings[4].text, "X <= Y <= Z is equivalent to (X <= Y) <= Z; did you mean X <= Y and Y <= Z?");
}
TEST_CASE_FIXTURE(Fixture, "RedundantNativeAttribute")
{
ScopedFastFlag sff[] = {{FFlag::LuauNativeAttribute, true}, {FFlag::LintRedundantNativeAttribute, true}};
LintResult result = lint(R"(
--!native
@native
local function f(a)
@native
local function g(b)
return (a + b)
end
return g
end
f(3)(4)
)");
REQUIRE(2 == result.warnings.size());
CHECK_EQ(result.warnings[0].text, "native attribute on a function is redundant in a native module; consider removing it");
CHECK_EQ(result.warnings[0].location, Location(Position(3, 0), Position(3, 7)));
CHECK_EQ(result.warnings[1].text, "native attribute on a function is redundant in a native module; consider removing it");
CHECK_EQ(result.warnings[1].location, Location(Position(5, 4), Position(5, 11)));
}
TEST_SUITE_END();