luau/tests/Frontend.test.cpp

1280 lines
38 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AstQuery.h"
#include "Luau/BuiltinDefinitions.h"
#include "Luau/Frontend.h"
#include "Luau/RequireTracer.h"
#include "Fixture.h"
#include "doctest.h"
#include <algorithm>
using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake 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: Vighnesh Vijay <vvijay@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>
2023-12-02 15:46:57 +08:00
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(CorrectEarlyReturnInMarkDirty);
namespace
{
struct NaiveModuleResolver : ModuleResolver
{
std::optional<ModuleInfo> resolveModuleInfo(const ModuleName& currentModuleName, const AstExpr& pathExpr) override
{
if (auto name = pathExprToModuleName(currentModuleName, pathExpr))
return {{*name, false}};
return std::nullopt;
}
const ModulePtr getModule(const ModuleName& moduleName) const override
{
return nullptr;
}
bool moduleExists(const ModuleName& moduleName) const override
{
return false;
}
std::string getHumanReadableModuleName(const ModuleName& moduleName) const override
{
return moduleName;
}
};
NaiveModuleResolver naiveModuleResolver;
struct NaiveFileResolver : NullFileResolver
{
std::optional<ModuleInfo> resolveModule(const ModuleInfo* context, AstExpr* expr) override
{
if (AstExprGlobal* g = expr->as<AstExprGlobal>())
{
if (g->name == "Modules")
return ModuleInfo{"Modules"};
if (g->name == "game")
return ModuleInfo{"game"};
}
else if (AstExprIndexName* i = expr->as<AstExprIndexName>())
{
if (context)
return ModuleInfo{context->name + '/' + i->index.value, context->optional};
}
else if (AstExprCall* call = expr->as<AstExprCall>(); call && call->self && call->args.size >= 1 && context)
{
if (AstExprConstantString* index = call->args.data[0]->as<AstExprConstantString>())
{
AstName func = call->func->as<AstExprIndexName>()->index;
if (func == "GetService" && context->name == "game")
return ModuleInfo{"game/" + std::string(index->value.data, index->value.size)};
}
}
return std::nullopt;
}
};
} // namespace
2022-05-14 03:36:37 +08:00
struct FrontendFixture : BuiltinsFixture
{
FrontendFixture()
{
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
}
};
TEST_SUITE_BEGIN("FrontendTest");
TEST_CASE_FIXTURE(FrontendFixture, "find_a_require")
{
AstStatBlock* program = parse(R"(
local M = require(Modules.Foo.Bar)
)");
NaiveFileResolver naiveFileResolver;
auto res = traceRequires(&naiveFileResolver, program, "");
2022-06-17 09:05:14 +08:00
CHECK_EQ(1, res.requireList.size());
CHECK_EQ(res.requireList[0].first, "Modules/Foo/Bar");
}
// It could be argued that this should not work.
TEST_CASE_FIXTURE(FrontendFixture, "find_a_require_inside_a_function")
{
AstStatBlock* program = parse(R"(
function foo()
local M = require(Modules.Foo.Bar)
end
)");
NaiveFileResolver naiveFileResolver;
auto res = traceRequires(&naiveFileResolver, program, "");
2022-06-17 09:05:14 +08:00
CHECK_EQ(1, res.requireList.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "real_source")
{
AstStatBlock* program = parse(R"(
return function()
local Modules = game:GetService("CoreGui").Gui.Modules
local Roact = require(Modules.Common.Roact)
local Rodux = require(Modules.Common.Rodux)
local AppReducer = require(Modules.LuaApp.AppReducer)
local AEAppReducer = require(Modules.LuaApp.Reducers.AEReducers.AEAppReducer)
local AETabList = require(Modules.LuaApp.Components.Avatar.UI.Views.Portrait.AETabList)
local mockServices = require(Modules.LuaApp.TestHelpers.mockServices)
local DeviceOrientationMode = require(Modules.LuaApp.DeviceOrientationMode)
local MockAvatarEditorTheme = require(Modules.LuaApp.TestHelpers.MockAvatarEditorTheming)
local FFlagAvatarEditorEnableThemes = settings():GetFFlag("AvatarEditorEnableThemes2")
end
)");
NaiveFileResolver naiveFileResolver;
auto res = traceRequires(&naiveFileResolver, program, "");
2022-06-17 09:05:14 +08:00
CHECK_EQ(8, res.requireList.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts")
{
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {b_value = A.hello}
)";
frontend.check("game/Gui/Modules/B");
ModulePtr bModule = frontend.moduleResolver.getModule("game/Gui/Modules/B");
REQUIRE(bModule != nullptr);
CHECK(bModule->errors.empty());
Luau::dumpErrors(bModule);
auto bExports = first(bModule->returnType);
REQUIRE(!!bExports);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ b_value: number }", toString(*bExports));
else
CHECK_EQ("{| b_value: number |}", toString(*bExports));
}
TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_cyclically_dependent_scripts")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
require(Modules.C)
return {}
)";
fileResolver.source["game/Gui/Modules/C"] = R"(
local Modules = game:GetService('Gui').Modules
do local A = require(Modules.A) end
return {}
)";
fileResolver.source["game/Gui/Modules/D"] = R"(
local Modules = game:GetService('Gui').Modules
do local A = require(Modules.A) end
return {}
)";
CheckResult result1 = frontend.check("game/Gui/Modules/B");
LUAU_REQUIRE_ERROR_COUNT(4, result1);
CHECK_MESSAGE(get<ModuleHasCyclicDependency>(result1.errors[0]), "Should have been a ModuleHasCyclicDependency: " << toString(result1.errors[0]));
CHECK_MESSAGE(get<ModuleHasCyclicDependency>(result1.errors[1]), "Should have been a ModuleHasCyclicDependency: " << toString(result1.errors[1]));
CHECK_MESSAGE(get<ModuleHasCyclicDependency>(result1.errors[2]), "Should have been a ModuleHasCyclicDependency: " << toString(result1.errors[2]));
CheckResult result2 = frontend.check("game/Gui/Modules/D");
LUAU_REQUIRE_ERROR_COUNT(0, result2);
}
TEST_CASE_FIXTURE(FrontendFixture, "any_annotation_breaks_cycle")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A) :: any
return {hello = A.hello}
)";
CheckResult result = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(FrontendFixture, "nocheck_modules_are_typed")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!nocheck
export type Foo = number
return {hello = "hi"}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!nonstrict
export type Foo = number
return {hello = "hi"}
)";
fileResolver.source["game/Gui/Modules/C"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
local B = require(Modules.B)
local five : A.Foo = 5
)";
CheckResult result = frontend.check("game/Gui/Modules/C");
LUAU_REQUIRE_NO_ERRORS(result);
ModulePtr aModule = frontend.moduleResolver.getModule("game/Gui/Modules/A");
REQUIRE(bool(aModule));
std::optional<TypeId> aExports = first(aModule->returnType);
REQUIRE(bool(aExports));
ModulePtr bModule = frontend.moduleResolver.getModule("game/Gui/Modules/B");
REQUIRE(bool(bModule));
std::optional<TypeId> bExports = first(bModule->returnType);
REQUIRE(bool(bExports));
CHECK_EQ(toString(*aExports), toString(*bExports));
}
TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_between_check_and_nocheck")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!nocheck
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {hello = A.hello}
)";
CheckResult result = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(1, result);
}
TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!nocheck
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!nocheck
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {hello = A.hello}
)";
fileResolver.source["game/Gui/Modules/C"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
local B = require(Modules.B)
return {a=A, b=B}
)";
CheckResult result = frontend.check("game/Gui/Modules/C");
LUAU_REQUIRE_NO_ERRORS(result);
ModulePtr cModule = frontend.moduleResolver.getModule("game/Gui/Modules/C");
REQUIRE(bool(cModule));
std::optional<TypeId> cExports = first(cModule->returnType);
REQUIRE(bool(cExports));
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ a: any, b: any }", toString(*cExports));
else
CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
}
TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_disabled_in_nocheck")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
--!nocheck
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
--!nocheck
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {hello = A.hello}
)";
CheckResult result = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(FrontendFixture, "cycle_errors_can_be_fixed")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {hello = A.hello}
)";
CheckResult result1 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
CHECK_MESSAGE(get<ModuleHasCyclicDependency>(result1.errors[0]), "Should have been a ModuleHasCyclicDependency: " << toString(result1.errors[0]));
CHECK_MESSAGE(get<ModuleHasCyclicDependency>(result1.errors[1]), "Should have been a ModuleHasCyclicDependency: " << toString(result1.errors[1]));
fileResolver.source["game/Gui/Modules/B"] = R"(
return {hello = 42}
)";
frontend.markDirty("game/Gui/Modules/B");
CheckResult result2 = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_NO_ERRORS(result2);
}
TEST_CASE_FIXTURE(FrontendFixture, "cycle_error_paths")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {hello = B.hello}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {hello = A.hello}
)";
CheckResult result = frontend.check("game/Gui/Modules/A");
LUAU_REQUIRE_ERROR_COUNT(2, result);
auto ce1 = get<ModuleHasCyclicDependency>(result.errors[0]);
REQUIRE(ce1);
CHECK_EQ(result.errors[0].moduleName, "game/Gui/Modules/B");
REQUIRE_EQ(ce1->cycle.size(), 2);
CHECK_EQ(ce1->cycle[0], "game/Gui/Modules/A");
CHECK_EQ(ce1->cycle[1], "game/Gui/Modules/B");
auto ce2 = get<ModuleHasCyclicDependency>(result.errors[1]);
REQUIRE(ce2);
CHECK_EQ(result.errors[1].moduleName, "game/Gui/Modules/A");
REQUIRE_EQ(ce2->cycle.size(), 2);
CHECK_EQ(ce2->cycle[0], "game/Gui/Modules/B");
CHECK_EQ(ce2->cycle[1], "game/Gui/Modules/A");
}
2022-04-08 05:29:01 +08:00
TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface")
{
fileResolver.source["game/A"] = R"(
return {hello = 2}
)";
CheckResult result = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(result);
fileResolver.source["game/A"] = R"(
local me = require(game.A)
return {hello = 2}
)";
frontend.markDirty("game/A");
result = frontend.check("game/A");
LUAU_REQUIRE_ERRORS(result);
auto ty = requireType("game/A", "me");
CHECK_EQ(toString(ty), "any");
}
TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_longer")
{
fileResolver.source["game/A"] = R"(
return {mod_a = 2}
)";
CheckResult result = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(result);
fileResolver.source["game/B"] = R"(
local me = require(game.A)
return {mod_b = 4}
)";
result = frontend.check("game/B");
LUAU_REQUIRE_NO_ERRORS(result);
fileResolver.source["game/A"] = R"(
local me = require(game.B)
return {mod_a_prime = 3}
)";
frontend.markDirty("game/A");
frontend.markDirty("game/B");
result = frontend.check("game/A");
LUAU_REQUIRE_ERRORS(result);
TypeId tyA = requireType("game/A", "me");
CHECK_EQ(toString(tyA), "any");
result = frontend.check("game/B");
LUAU_REQUIRE_ERRORS(result);
TypeId tyB = requireType("game/B", "me");
CHECK_EQ(toString(tyB), "any");
}
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 23:13:53 +08:00
TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_exports")
{
fileResolver.source["game/A"] = R"(
local b = require(game.B)
export type atype = { x: b.btype }
return {mod_a = 1}
)";
fileResolver.source["game/B"] = R"(
export type btype = { x: number }
local function bf()
local a = require(game.A)
local bfl : a.atype = nil
return {bfl.x}
end
return {mod_b = 2}
)";
ToStringOptions opts;
opts.exhaustive = true;
CheckResult resultA = frontend.check("game/A");
LUAU_REQUIRE_ERRORS(resultA);
CheckResult resultB = frontend.check("game/B");
LUAU_REQUIRE_ERRORS(resultB);
TypeId tyB = requireExportedType("game/B", "btype");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(tyB, opts), "{ x: number }");
else
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
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 23:13:53 +08:00
TypeId tyA = requireExportedType("game/A", "atype");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(tyA, opts), "{ x: any }");
else
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
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 23:13:53 +08:00
frontend.markDirty("game/B");
resultB = frontend.check("game/B");
LUAU_REQUIRE_ERRORS(resultB);
tyB = requireExportedType("game/B", "btype");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(tyB, opts), "{ x: number }");
else
CHECK_EQ(toString(tyB, opts), "{| x: number |}");
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 23:13:53 +08:00
tyA = requireExportedType("game/A", "atype");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(tyA, opts), "{ x: any }");
else
CHECK_EQ(toString(tyA, opts), "{| x: any |}");
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 23:13:53 +08:00
}
TEST_CASE_FIXTURE(FrontendFixture, "dont_reparse_clean_file_when_linting")
{
fileResolver.source["Modules/A"] = R"(
local t = {}
for i=#t,1 do
end
for i=#t,1,-1 do
end
)";
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-18 03:20:37 +08:00
configResolver.defaultConfig.enabledLint.enableWarning(LintWarning::Code_ForRange);
lintModule("Modules/A");
fileResolver.source["Modules/A"] = R"(
-- We have fixed the lint error, but we did not tell the Frontend that the file is changed!
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-18 03:20:37 +08:00
-- Therefore, we expect Frontend to reuse the results from previous lint.
)";
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-18 03:20:37 +08:00
LintResult lintResult = lintModule("Modules/A");
CHECK_EQ(1, lintResult.warnings.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "dont_recheck_script_that_hasnt_been_marked_dirty")
{
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {b_value = A.hello}
)";
frontend.check("game/Gui/Modules/B");
fileResolver.source["game/Gui/Modules/A"] =
"Massively incorrect syntax haha oops! However! The frontend doesn't know that this file needs reparsing!";
frontend.check("game/Gui/Modules/B");
ModulePtr bModule = frontend.moduleResolver.getModule("game/Gui/Modules/B");
CHECK(bModule->errors.empty());
Luau::dumpErrors(bModule);
}
TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty")
{
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = game:GetService('Gui').Modules
local A = require(Modules.A)
return {b_value = A.hello}
)";
frontend.check("game/Gui/Modules/B");
fileResolver.source["game/Gui/Modules/A"] = "return {hello='hi!'}";
frontend.markDirty("game/Gui/Modules/A");
frontend.check("game/Gui/Modules/B");
ModulePtr bModule = frontend.moduleResolver.getModule("game/Gui/Modules/B");
CHECK(bModule->errors.empty());
Luau::dumpErrors(bModule);
auto bExports = first(bModule->returnType);
REQUIRE(!!bExports);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ b_value: string }", toString(*bExports));
else
CHECK_EQ("{| b_value: string |}", toString(*bExports));
}
TEST_CASE_FIXTURE(FrontendFixture, "mark_non_immediate_reverse_deps_as_dirty")
{
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
fileResolver.source["game/Gui/Modules/B"] = R"(
return require(game:GetService('Gui').Modules.A)
)";
fileResolver.source["game/Gui/Modules/C"] = R"(
local Modules = game:GetService('Gui').Modules
local B = require(Modules.B)
return {c_value = B.hello}
)";
frontend.check("game/Gui/Modules/C");
std::vector<Luau::ModuleName> markedDirty;
frontend.markDirty("game/Gui/Modules/A", &markedDirty);
REQUIRE(markedDirty.size() == 3);
CHECK(std::find(markedDirty.begin(), markedDirty.end(), "game/Gui/Modules/A") != markedDirty.end());
CHECK(std::find(markedDirty.begin(), markedDirty.end(), "game/Gui/Modules/B") != markedDirty.end());
CHECK(std::find(markedDirty.begin(), markedDirty.end(), "game/Gui/Modules/C") != markedDirty.end());
}
#if 0
// Does not work yet. :(
TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_has_a_parse_error")
{
fileResolver.source["Modules/A"] = "oh no a syntax error";
fileResolver.source["Modules/B"] = R"(
local Modules = {}
local A = require(Modules.A)
return {}
)";
CheckResult result = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("Modules/A", result.errors[0].moduleName);
CheckResult result2 = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result2);
CHECK_EQ(result2.errors[0], result.errors[0]);
}
#endif
TEST_CASE_FIXTURE(FrontendFixture, "produce_errors_for_unchanged_file_with_a_syntax_error")
{
fileResolver.source["Modules/A"] = "oh no a blatant syntax error!!";
CheckResult one = frontend.check("Modules/A");
CheckResult two = frontend.check("Modules/A");
CHECK(!one.errors.empty());
CHECK(!two.errors.empty());
}
TEST_CASE_FIXTURE(FrontendFixture, "produce_errors_for_unchanged_file_with_errors")
{
fileResolver.source["Modules/A"] = "local p: number = 'oh no a type error'";
frontend.check("Modules/A");
fileResolver.source["Modules/A"] = "local p = 4 -- We have fixed the problem, but we didn't tell the frontend, so it will not recheck this file!";
CheckResult secondResult = frontend.check("Modules/A");
CHECK_EQ(1, secondResult.errors.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "reports_errors_from_multiple_sources")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local a: number = 'oh no a type error'
return {a=a}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = script.Parent
local A = require(Modules.A)
local b: number = 'another one! This is quite distressing!'
)";
CheckResult result = frontend.check("game/Gui/Modules/B");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ("game/Gui/Modules/A", result.errors[0].moduleName);
CHECK_EQ("game/Gui/Modules/B", result.errors[1].moduleName);
}
TEST_CASE_FIXTURE(FrontendFixture, "report_require_to_nonexistent_file")
{
fileResolver.source["Modules/A"] = R"(
local Modules = script
local B = require(Modules.B)
)";
CheckResult result = frontend.check("Modules/A");
LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string s = toString(result.errors[0]);
CHECK_MESSAGE(get<UnknownRequire>(result.errors[0]), "Should have been an UnknownRequire: " << toString(result.errors[0]));
}
TEST_CASE_FIXTURE(FrontendFixture, "ignore_require_to_nonexistent_file")
{
fileResolver.source["Modules/A"] = R"(
local Modules = script
local B = require(Modules.B) :: any
)";
CheckResult result = frontend.check("Modules/A");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(FrontendFixture, "report_syntax_error_in_required_file")
{
fileResolver.source["Modules/A"] = "oh no a gross breach of syntax";
fileResolver.source["Modules/B"] = R"(
local Modules = script.Parent
local A = require(Modules.A)
)";
CheckResult result = frontend.check("Modules/B");
LUAU_REQUIRE_ERRORS(result);
CHECK_EQ("Modules/A", result.errors[0].moduleName);
bool b = std::any_of(begin(result.errors), end(result.errors), [](auto&& e) -> bool {
return get<SyntaxError>(e);
});
if (!b)
{
CHECK_MESSAGE(false, "Expected a syntax error!");
dumpErrors(result);
}
}
TEST_CASE_FIXTURE(FrontendFixture, "re_report_type_error_in_required_file")
{
fileResolver.source["Modules/A"] = R"(
local n: number = 'five'
return {n=n}
)";
fileResolver.source["Modules/B"] = R"(
local Modules = script.Parent
local A = require(Modules.A)
print(A.n)
)";
CheckResult result = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CheckResult result2 = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result2);
CHECK_EQ("Modules/A", result.errors[0].moduleName);
}
TEST_CASE_FIXTURE(FrontendFixture, "accumulate_cached_errors")
{
fileResolver.source["Modules/A"] = R"(
local n: number = 'five'
return {n=n}
)";
fileResolver.source["Modules/B"] = R"(
local Modules = script.Parent
local A = require(Modules.A)
local b: number = 'seven'
print(A, b)
)";
CheckResult result1 = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(2, result1);
CHECK_EQ("Modules/A", result1.errors[0].moduleName);
CHECK_EQ("Modules/B", result1.errors[1].moduleName);
CheckResult result2 = frontend.check("Modules/B");
LUAU_REQUIRE_ERROR_COUNT(2, result2);
CHECK_EQ("Modules/A", result2.errors[0].moduleName);
CHECK_EQ("Modules/B", result2.errors[1].moduleName);
}
TEST_CASE_FIXTURE(FrontendFixture, "accumulate_cached_errors_in_consistent_order")
{
fileResolver.source["Modules/A"] = R"(
a = 1
b = 2
local Modules = script.Parent
local A = require(Modules.B)
)";
fileResolver.source["Modules/B"] = R"(
d = 3
e = 4
return {}
)";
CheckResult result1 = frontend.check("Modules/A");
LUAU_REQUIRE_ERROR_COUNT(4, result1);
CHECK_EQ("Modules/A", result1.errors[2].moduleName);
CHECK_EQ("Modules/A", result1.errors[3].moduleName);
CHECK_EQ("Modules/B", result1.errors[0].moduleName);
CHECK_EQ("Modules/B", result1.errors[1].moduleName);
CheckResult result2 = frontend.check("Modules/A");
CHECK_EQ(4, result2.errors.size());
for (size_t i = 0; i < result1.errors.size(); ++i)
CHECK_EQ(result1.errors[i], result2.errors[i]);
}
TEST_CASE_FIXTURE(FrontendFixture, "test_pruneParentSegments")
{
CHECK_EQ(std::optional<std::string>{"Modules/Enum/ButtonState"},
pathExprToModuleName("", {"Modules", "LuaApp", "DeprecatedDarkTheme", "Parent", "Parent", "Enum", "ButtonState"}));
CHECK_EQ(std::optional<std::string>{"workspace/Foo/Bar/Baz"}, pathExprToModuleName("workspace/Foo/Quux", {"script", "Parent", "Bar", "Baz"}));
CHECK_EQ(std::nullopt, pathExprToModuleName("", {}));
CHECK_EQ(std::optional<std::string>{"script"}, pathExprToModuleName("", {"script"}));
CHECK_EQ(std::optional<std::string>{"script/Parent"}, pathExprToModuleName("", {"script", "Parent"}));
CHECK_EQ(std::optional<std::string>{"script"}, pathExprToModuleName("", {"script", "Parent", "Parent"}));
CHECK_EQ(std::optional<std::string>{"script"}, pathExprToModuleName("", {"script", "Test", "Parent"}));
CHECK_EQ(std::optional<std::string>{"script/Parent"}, pathExprToModuleName("", {"script", "Test", "Parent", "Parent"}));
CHECK_EQ(std::optional<std::string>{"script/Parent"}, pathExprToModuleName("", {"script", "Test", "Parent", "Test", "Parent", "Parent"}));
}
TEST_CASE_FIXTURE(FrontendFixture, "test_lint_uses_correct_config")
{
fileResolver.source["Module/A"] = R"(
local t = {}
for i=#t,1 do
end
)";
configResolver.configFiles["Module/A"].enabledLint.enableWarning(LintWarning::Code_ForRange);
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-18 03:20:37 +08:00
auto result = lintModule("Module/A");
CHECK_EQ(1, result.warnings.size());
configResolver.configFiles["Module/A"].enabledLint.disableWarning(LintWarning::Code_ForRange);
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-18 03:20:37 +08:00
frontend.markDirty("Module/A");
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-18 03:20:37 +08:00
auto result2 = lintModule("Module/A");
CHECK_EQ(0, result2.warnings.size());
LintOptions overrideOptions;
overrideOptions.enableWarning(LintWarning::Code_ForRange);
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-18 03:20:37 +08:00
frontend.markDirty("Module/A");
auto result3 = lintModule("Module/A", overrideOptions);
CHECK_EQ(1, result3.warnings.size());
overrideOptions.disableWarning(LintWarning::Code_ForRange);
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-18 03:20:37 +08:00
frontend.markDirty("Module/A");
auto result4 = lintModule("Module/A", overrideOptions);
CHECK_EQ(0, result4.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-18 03:20:37 +08:00
TEST_CASE_FIXTURE(FrontendFixture, "lint_results_are_only_for_checked_module")
{
fileResolver.source["Module/A"] = R"(
local _ = 0b10000000000000000000000000000000000000000000000000000000000000000
)";
fileResolver.source["Module/B"] = R"(
require(script.Parent.A)
local _ = 0x10000000000000000
)";
LintResult lintResult = lintModule("Module/B");
CHECK_EQ(1, lintResult.warnings.size());
// Check cached result
lintResult = lintModule("Module/B");
CHECK_EQ(1, lintResult.warnings.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "discard_type_graphs")
{
Frontend fe{&fileResolver, &configResolver, {false}};
fileResolver.source["Module/A"] = R"(
local a = {1,2,3,4,5}
)";
CheckResult result = fe.check("Module/A");
ModulePtr module = fe.moduleResolver.getModule("Module/A");
CHECK_EQ(0, module->internalTypes.types.size());
CHECK_EQ(0, module->internalTypes.typePacks.size());
CHECK_EQ(0, module->astTypes.size());
CHECK_EQ(0, module->astResolvedTypes.size());
CHECK_EQ(0, module->astResolvedTypePacks.size());
}
TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded")
{
Frontend fe{&fileResolver, &configResolver, {false}};
fileResolver.source["Module/A"] = R"(
--!strict
local a: {Count: number} = {count='five'}
)";
CheckResult result = fe.check("Module/A");
REQUIRE_EQ(1, result.errors.size());
// When this test fails, it is because the TypeIds needed by the error have been deallocated.
// It is thus basically impossible to predict what will happen when this assert is evaluated.
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
if (FFlag::DebugLuauDeferredConstraintResolution)
REQUIRE_EQ(
"Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]));
else
REQUIRE_EQ(
"Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(FrontendFixture, "trace_requires_in_nonstrict_mode")
{
fileResolver.source["Module/A"] = R"(
--!nonstrict
local module = {}
function module.f(arg: number)
print('f', arg)
end
return module
)";
fileResolver.source["Module/B"] = R"(
--!nonstrict
local A = require(script.Parent.A)
print(A.g(5)) -- Key 'g' not found
print(A.f('five')) -- Type mismatch number and string
print(A.f(5)) -- OK
)";
CheckResult result = frontend.check("Module/B");
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(4, result.errors[0].location.begin.line);
CHECK_EQ(5, result.errors[1].location.begin.line);
}
TEST_CASE_FIXTURE(FrontendFixture, "environments")
{
ScopePtr testScope = frontend.addEnvironment("test");
unfreeze(frontend.globals.globalTypes);
frontend.loadDefinitionFile(frontend.globals, testScope, R"(
export type Foo = number | string
)",
"@test", /* captureComments */ false);
freeze(frontend.globals.globalTypes);
fileResolver.source["A"] = R"(
--!nonstrict
local foo: Foo = 1
)";
fileResolver.source["B"] = R"(
--!nonstrict
local foo: Foo = 1
)";
fileResolver.environments["A"] = "test";
CheckResult resultA = frontend.check("A");
LUAU_REQUIRE_NO_ERRORS(resultA);
CheckResult resultB = frontend.check("B");
LUAU_REQUIRE_ERROR_COUNT(1, resultB);
}
TEST_CASE_FIXTURE(FrontendFixture, "ast_node_at_position")
{
check(R"(
local t = {}
function t:aa() end
t:
)");
SourceModule* module = getMainSourceModule();
Position pos = module->root->location.end;
AstNode* node = findNodeAtPosition(*module, pos);
REQUIRE(node);
REQUIRE(bool(node->asExpr()));
++pos.column;
AstNode* node2 = findNodeAtPosition(*module, pos);
CHECK_EQ(node, node2);
}
TEST_CASE_FIXTURE(FrontendFixture, "stats_are_not_reset_between_checks")
{
fileResolver.source["Module/A"] = R"(
--!strict
local B = require(script.Parent.B)
local foo = B.foo + 1
)";
fileResolver.source["Module/B"] = R"(
--!strict
return {foo = 1}
)";
CheckResult r1 = frontend.check("Module/A");
LUAU_REQUIRE_NO_ERRORS(r1);
Frontend::Stats stats1 = frontend.stats;
CHECK_EQ(2, stats1.files);
frontend.markDirty("Module/A");
frontend.markDirty("Module/B");
CheckResult r2 = frontend.check("Module/A");
LUAU_REQUIRE_NO_ERRORS(r2);
Frontend::Stats stats2 = frontend.stats;
CHECK_EQ(4, stats2.files);
}
TEST_CASE_FIXTURE(FrontendFixture, "clearStats")
{
fileResolver.source["Module/A"] = R"(
--!strict
local B = require(script.Parent.B)
local foo = B.foo + 1
)";
fileResolver.source["Module/B"] = R"(
--!strict
return {foo = 1}
)";
CheckResult r1 = frontend.check("Module/A");
LUAU_REQUIRE_NO_ERRORS(r1);
Frontend::Stats stats1 = frontend.stats;
CHECK_EQ(2, stats1.files);
frontend.markDirty("Module/A");
frontend.markDirty("Module/B");
frontend.clearStats();
CheckResult r2 = frontend.check("Module/A");
LUAU_REQUIRE_NO_ERRORS(r2);
Frontend::Stats stats2 = frontend.stats;
CHECK_EQ(2, stats2.files);
}
TEST_CASE_FIXTURE(FrontendFixture, "typecheck_twice_for_ast_types")
{
fileResolver.source["Module/A"] = R"(
local a = 1
)";
CheckResult result = frontend.check("Module/A");
ModulePtr module = frontend.moduleResolver.getModule("Module/A");
REQUIRE_EQ(module->astTypes.size(), 1);
auto it = module->astTypes.begin();
CHECK_EQ(toString(it->second), "number");
}
TEST_CASE_FIXTURE(FrontendFixture, "imported_table_modification_2")
{
frontend.options.retainFullTypeGraphs = false;
fileResolver.source["Module/A"] = R"(
--!nonstrict
local a = {}
a.x = 1
return a;
)";
fileResolver.source["Module/B"] = R"(
--!nonstrict
local a = require(script.Parent.A)
local b = {}
function a:b() end -- this should error, since A doesn't define a:b()
return b
)";
fileResolver.source["Module/C"] = R"(
--!nonstrict
local a = require(script.Parent.A)
local b = require(script.Parent.B)
a:b() -- this should error, since A doesn't define a:b()
)";
CheckResult resultA = frontend.check("Module/A");
LUAU_REQUIRE_NO_ERRORS(resultA);
CheckResult resultB = frontend.check("Module/B");
LUAU_REQUIRE_ERRORS(resultB);
CheckResult resultC = frontend.check("Module/C");
LUAU_REQUIRE_ERRORS(resultC);
}
// This test does not use TEST_CASE_FIXTURE because we need to set a flag before
// the fixture is constructed.
TEST_CASE("no_use_after_free_with_type_fun_instantiation")
{
// This flag forces this test to crash if there's a UAF in this code.
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake 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: Vighnesh Vijay <vvijay@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>
2023-12-02 15:46:57 +08:00
ScopedFastFlag sff_DebugLuauFreezeArena(FFlag::DebugLuauFreezeArena, true);
FrontendFixture fix;
fix.fileResolver.source["Module/A"] = R"(
export type Foo<V> = typeof(setmetatable({}, {}))
return false;
)";
fix.fileResolver.source["Module/B"] = R"(
local A = require(script.Parent.A)
export type Foo<V> = A.Foo<V>
return false;
)";
// We don't care about the result. That we haven't crashed is enough.
fix.frontend.check("Module/B");
}
2022-04-29 09:24:24 +08:00
TEST_CASE("check_without_builtin_next")
{
TestFileResolver fileResolver;
TestConfigResolver configResolver;
Frontend frontend(&fileResolver, &configResolver);
fileResolver.source["Module/A"] = "for k,v in 2 do end";
fileResolver.source["Module/B"] = "return next";
// We don't care about the result. That we haven't crashed is enough.
frontend.check("Module/A");
frontend.check("Module/B");
}
2022-07-22 05:16:54 +08:00
TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_cyclic_type")
{
fileResolver.source["Module/A"] = R"(
type F<T> = (set: G<T>) -> ()
export type G<T> = {
forEach: (a: F<T>) -> (),
}
function X<T>(a: F<T>): ()
end
return X
)";
fileResolver.source["Module/B"] = R"(
--!strict
local A = require(script.Parent.A)
export type G<T> = A.G<T>
return {
A = A,
}
)";
CheckResult result = frontend.check("Module/B");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "reexport_type_alias")
{
fileResolver.source["Module/A"] = R"(
type KeyOfTestEvents = "test-file-start" | "test-file-success" | "test-file-failure" | "test-case-result"
type MyAny = any
2022-07-22 05:16:54 +08:00
export type TestFileEvent<T = KeyOfTestEvents> = (
eventName: T,
args: any --[[ ROBLOX TODO: Unhandled node for type: TSIndexedAccessType ]] --[[ TestEvents[T] ]]
) -> MyAny
2022-07-22 05:16:54 +08:00
return {}
)";
fileResolver.source["Module/B"] = R"(
--!strict
local A = require(script.Parent.A)
export type TestFileEvent = A.TestFileEvent
)";
CheckResult result = frontend.check("Module/B");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "module_scope_check")
{
frontend.prepareModuleScope = [this](const ModuleName& name, const ScopePtr& scope, bool forAutocomplete) {
scope->bindings[Luau::AstName{"x"}] = Luau::Binding{frontend.globals.builtinTypes->numberType};
};
fileResolver.source["game/A"] = R"(
local a = x
)";
CheckResult result = frontend.check("game/A");
LUAU_REQUIRE_NO_ERRORS(result);
auto ty = requireType("game/A", "a");
CHECK_EQ(toString(ty), "number");
}
TEST_CASE_FIXTURE(FrontendFixture, "parse_only")
{
fileResolver.source["game/Gui/Modules/A"] = R"(
local a: number = 'oh no a type error'
return {a=a}
)";
fileResolver.source["game/Gui/Modules/B"] = R"(
local Modules = script.Parent
local A = require(Modules.A)
local b: number = 2
)";
frontend.parse("game/Gui/Modules/B");
REQUIRE(frontend.sourceNodes.count("game/Gui/Modules/A"));
REQUIRE(frontend.sourceNodes.count("game/Gui/Modules/B"));
auto node = frontend.sourceNodes["game/Gui/Modules/B"];
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-11 05:10:07 +08:00
CHECK(node->requireSet.contains("game/Gui/Modules/A"));
REQUIRE_EQ(node->requireLocations.size(), 1);
CHECK_EQ(node->requireLocations[0].second, Luau::Location(Position(2, 18), Position(2, 36)));
// Early parse doesn't cause typechecking to be skipped
CheckResult result = frontend.check("game/Gui/Modules/B");
LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ("game/Gui/Modules/A", result.errors[0].moduleName);
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(FrontendFixture, "markdirty_early_return")
{
Sync to upstream/release/605 (#1118) - Implemented [Require by String with Relative Paths](https://github.com/luau-lang/rfcs/blob/master/docs/new-require-by-string-semantics.md) RFC - Implemented [Require by String with Aliases](https://github.com/luau-lang/rfcs/blob/master/docs/require-by-string-aliases.md) RFC with support for `paths` and `alias` arrays in .luarc - Added SUBRK and DIVRK bytecode instructions to speed up constant-number and constant/number operations - Added `--vector-lib`, `--vector-ctor` and `--vector-type` options to luau-compile to support code with vectors New Solver - Correctness fixes to subtyping - Improvements to dataflow analysis Native Code Generation - Added bytecode analysis pass to predict type tags used in operations - Fixed rare cases of numerical loops being generated without an interrupt instruction - Restored optimization data propagation into the linear block - Duplicate buffer length checks are optimized away Miscellaneous - Small performance improvements to new non-strict mode - Introduced more scripts for fuzzing Luau and processing the results, including fuzzer build support for CMake 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: Vighnesh Vijay <vvijay@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>
2023-12-02 15:46:57 +08:00
ScopedFastFlag fflag(FFlag::CorrectEarlyReturnInMarkDirty, true);
constexpr char moduleName[] = "game/Gui/Modules/A";
fileResolver.source[moduleName] = R"(
return 1
)";
{
std::vector<ModuleName> markedDirty;
frontend.markDirty(moduleName, &markedDirty);
CHECK(markedDirty.empty());
}
frontend.parse(moduleName);
{
std::vector<ModuleName> markedDirty;
frontend.markDirty(moduleName, &markedDirty);
CHECK(!markedDirty.empty());
}
}
TEST_SUITE_END();