mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 21:40:43 +00:00
c2ba1058c3
# 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>
320 lines
6.8 KiB
C++
320 lines
6.8 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/DataFlowGraph.h"
|
|
#include "Luau/Error.h"
|
|
#include "Luau/Parser.h"
|
|
|
|
#include "AstQueryDsl.h"
|
|
#include "ScopedFlags.h"
|
|
|
|
#include "doctest.h"
|
|
|
|
using namespace Luau;
|
|
|
|
struct DataFlowGraphFixture
|
|
{
|
|
// Only needed to fix the operator== reflexivity of an empty Symbol.
|
|
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
|
|
|
|
InternalErrorReporter handle;
|
|
|
|
Allocator allocator;
|
|
AstNameTable names{allocator};
|
|
AstStatBlock* module;
|
|
|
|
std::optional<DataFlowGraph> graph;
|
|
|
|
void dfg(const std::string& code)
|
|
{
|
|
ParseResult parseResult = Parser::parse(code.c_str(), code.size(), names, allocator);
|
|
if (!parseResult.errors.empty())
|
|
throw ParseErrors(std::move(parseResult.errors));
|
|
module = parseResult.root;
|
|
graph = DataFlowGraphBuilder::build(module, NotNull{&handle});
|
|
}
|
|
|
|
template<typename T, int N>
|
|
DefId getDef(const std::vector<Nth>& nths = {nth<T>(N)})
|
|
{
|
|
T* node = query<T, N>(module, nths);
|
|
REQUIRE(node);
|
|
return graph->getDef(node);
|
|
}
|
|
};
|
|
|
|
TEST_SUITE_BEGIN("DataFlowGraphBuilder");
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "define_locals_in_local_stat")
|
|
{
|
|
dfg(R"(
|
|
local x = 5
|
|
local y = x
|
|
)");
|
|
|
|
(void)getDef<AstExprLocal, 1>();
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "define_parameters_in_functions")
|
|
{
|
|
dfg(R"(
|
|
local function f(x)
|
|
local y = x
|
|
end
|
|
)");
|
|
|
|
(void)getDef<AstExprLocal, 1>();
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "find_aliases")
|
|
{
|
|
dfg(R"(
|
|
local x = 5
|
|
local y = x
|
|
local z = y
|
|
)");
|
|
|
|
DefId x = getDef<AstExprLocal, 1>();
|
|
DefId y = getDef<AstExprLocal, 2>();
|
|
REQUIRE(x != y);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "independent_locals")
|
|
{
|
|
dfg(R"(
|
|
local x = 5
|
|
local y = 5
|
|
|
|
local a = x
|
|
local b = y
|
|
)");
|
|
|
|
DefId x = getDef<AstExprLocal, 1>();
|
|
DefId y = getDef<AstExprLocal, 2>();
|
|
REQUIRE(x != y);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi")
|
|
{
|
|
dfg(R"(
|
|
local x
|
|
|
|
if a then
|
|
x = true
|
|
end
|
|
|
|
local y = x
|
|
)");
|
|
|
|
DefId y = getDef<AstExprLocal, 2>();
|
|
|
|
const Phi* phi = get<Phi>(y);
|
|
CHECK(phi);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while")
|
|
{
|
|
dfg(R"(
|
|
local x
|
|
|
|
while cond() do
|
|
x = true
|
|
end
|
|
|
|
local y = x
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
|
|
|
|
CHECK(x0 == x1);
|
|
CHECK(x1 == x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while")
|
|
{
|
|
dfg(R"(
|
|
while cond() do
|
|
local x
|
|
x = true
|
|
x = 5
|
|
end
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // x = 5
|
|
|
|
CHECK(x0 != x1);
|
|
CHECK(x1 != x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat")
|
|
{
|
|
dfg(R"(
|
|
local x
|
|
|
|
repeat
|
|
x = true
|
|
until cond()
|
|
|
|
local y = x
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
|
|
|
|
CHECK(x0 == x1);
|
|
CHECK(x1 == x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_repeat")
|
|
{
|
|
dfg(R"(
|
|
repeat
|
|
local x
|
|
x = true
|
|
x = 5
|
|
until cond()
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // x = 5
|
|
|
|
CHECK(x0 != x1);
|
|
CHECK(x1 != x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for")
|
|
{
|
|
dfg(R"(
|
|
local x
|
|
|
|
for i = 0, 5 do
|
|
x = true
|
|
end
|
|
|
|
local y = x
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
|
|
|
|
CHECK(x0 == x1);
|
|
CHECK(x1 == x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for")
|
|
{
|
|
dfg(R"(
|
|
for i = 0, 5 do
|
|
local x
|
|
x = true
|
|
x = 5
|
|
end
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // x = 5
|
|
|
|
CHECK(x0 != x1);
|
|
CHECK(x1 != x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in")
|
|
{
|
|
dfg(R"(
|
|
local x
|
|
|
|
for i, v in t do
|
|
x = true
|
|
end
|
|
|
|
local y = x
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
|
|
|
|
CHECK(x0 == x1);
|
|
CHECK(x1 == x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in")
|
|
{
|
|
dfg(R"(
|
|
for i, v in t do
|
|
local x
|
|
x = true
|
|
x = 5
|
|
end
|
|
)");
|
|
|
|
DefId x0 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]);
|
|
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
|
|
DefId x2 = getDef<AstExprLocal, 2>(); // x = 5
|
|
|
|
CHECK(x0 != x1);
|
|
CHECK(x1 != x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_by_while")
|
|
{
|
|
dfg(R"(
|
|
local t = {}
|
|
t.x = 5
|
|
|
|
while cond() do
|
|
t.x = true
|
|
end
|
|
|
|
local y = t.x
|
|
)");
|
|
|
|
DefId x1 = getDef<AstExprIndexName, 1>(); // t.x = 5
|
|
DefId x2 = getDef<AstExprIndexName, 2>(); // t.x = true
|
|
DefId x3 = getDef<AstExprIndexName, 3>(); // local y = t.x
|
|
|
|
CHECK(x1 == x2);
|
|
CHECK(x2 == x3);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_non_preexisting_property_not_owned_by_while")
|
|
{
|
|
dfg(R"(
|
|
local t = {}
|
|
|
|
while cond() do
|
|
t.x = true
|
|
end
|
|
|
|
local y = t.x
|
|
)");
|
|
|
|
DefId x1 = getDef<AstExprIndexName, 1>(); // t.x = true
|
|
DefId x2 = getDef<AstExprIndexName, 2>(); // local y = t.x
|
|
|
|
CHECK(x1 == x2);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_property_of_table_owned_by_while")
|
|
{
|
|
dfg(R"(
|
|
while cond() do
|
|
local t = {}
|
|
t.x = true
|
|
t.x = 5
|
|
end
|
|
)");
|
|
|
|
DefId x1 = getDef<AstExprIndexName, 1>(); // t.x = true
|
|
DefId x2 = getDef<AstExprIndexName, 2>(); // t.x = 5
|
|
|
|
CHECK(x1 != x2);
|
|
}
|
|
|
|
TEST_SUITE_END();
|