mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
d01addc625
Co-authored-by: Rodactor <rodactor@roblox.com>
414 lines
9.3 KiB
C++
414 lines
9.3 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/TopoSortStatements.h"
|
|
#include "Luau/Transpiler.h"
|
|
|
|
#include "Fixture.h"
|
|
|
|
#include "doctest.h"
|
|
|
|
using namespace Luau;
|
|
|
|
static std::vector<AstStat*> toposort(AstStatBlock& block)
|
|
{
|
|
std::vector<AstStat*> result{block.body.begin(), block.body.end()};
|
|
|
|
Luau::toposort(result);
|
|
|
|
return result;
|
|
}
|
|
|
|
TEST_SUITE_BEGIN("TopoSortTests");
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "sorts")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
function A()
|
|
return B("high five!")
|
|
end
|
|
|
|
function B(x)
|
|
return x
|
|
end
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
REQUIRE_EQ(2, sorted.size());
|
|
|
|
AstStatBlock* block = program->as<AstStatBlock>();
|
|
REQUIRE(block != nullptr);
|
|
REQUIRE_EQ(2, block->body.size);
|
|
|
|
// B is sorted ahead of A
|
|
CHECK_EQ(block->body.data[1], sorted[0]);
|
|
CHECK_EQ(block->body.data[0], sorted[1]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "cyclic_dependency_terminates")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
function A()
|
|
return B()
|
|
end
|
|
|
|
function B()
|
|
return A()
|
|
end
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
REQUIRE_EQ(2, sorted.size());
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "doesnt_omit_statements_that_dont_need_sorting")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local X = {}
|
|
|
|
function A()
|
|
return B(5), B("Hi")
|
|
end
|
|
|
|
local Y = {}
|
|
|
|
function B(x)
|
|
return x
|
|
end
|
|
|
|
local Z = B()
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
REQUIRE_EQ(5, sorted.size());
|
|
|
|
AstStatBlock* block = program->as<AstStatBlock>();
|
|
REQUIRE(block != nullptr);
|
|
REQUIRE_EQ(5, block->body.size);
|
|
|
|
AstStat* X = block->body.data[0];
|
|
AstStat* A = block->body.data[1];
|
|
AstStat* Y = block->body.data[2];
|
|
AstStat* B = block->body.data[3];
|
|
AstStat* Z = block->body.data[4];
|
|
|
|
CHECK_EQ(sorted[0], X);
|
|
CHECK_EQ(sorted[1], Y);
|
|
CHECK_EQ(sorted[2], B);
|
|
CHECK_EQ(sorted[3], Z);
|
|
CHECK_EQ(sorted[4], A);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "slightly_more_complex")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local T = {}
|
|
|
|
function T:foo()
|
|
return T:bar(999), T:bar("hi")
|
|
end
|
|
|
|
function T:bar(i)
|
|
return i
|
|
end
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(3, sorted.size());
|
|
|
|
CHECK_EQ(sorted[0], program->body.data[0]);
|
|
CHECK_EQ(sorted[1], program->body.data[2]);
|
|
CHECK_EQ(sorted[2], program->body.data[1]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "reorder_functions_after_dependent_assigns")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local T = {} -- 0
|
|
|
|
function T.a() -- 1 depends on (2)
|
|
T.b()
|
|
end
|
|
|
|
function T.b() -- 2 depends on (4)
|
|
T.c()
|
|
end
|
|
|
|
function make_function() -- 3
|
|
return function() end
|
|
end
|
|
|
|
T.c = make_function() -- 4 depends on (3)
|
|
|
|
T.a() -- 5 depends on (1)
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(6, sorted.size());
|
|
|
|
CHECK_EQ(sorted[0], program->body.data[0]);
|
|
CHECK_EQ(sorted[1], program->body.data[3]);
|
|
CHECK_EQ(sorted[2], program->body.data[4]);
|
|
CHECK_EQ(sorted[3], program->body.data[2]);
|
|
CHECK_EQ(sorted[4], program->body.data[1]);
|
|
CHECK_EQ(sorted[5], program->body.data[5]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "dont_reorder_assigns")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local T = {} -- 0
|
|
|
|
function T.a() -- 1 depends on (2)
|
|
T.b()
|
|
end
|
|
|
|
function T.b() -- 2 depends on (5)
|
|
T.c()
|
|
end
|
|
|
|
function make_function() -- 3
|
|
return function() end
|
|
end
|
|
|
|
T.a() -- 4 depends on (1 -> 2 -> 5), but we cannot reorder it after 5!
|
|
|
|
T.c = make_function() -- 5 depends on (3)
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(6, sorted.size());
|
|
|
|
CHECK_EQ(sorted[0], program->body.data[0]);
|
|
CHECK_EQ(sorted[1], program->body.data[3]);
|
|
CHECK_EQ(sorted[2], program->body.data[2]);
|
|
CHECK_EQ(sorted[3], program->body.data[1]);
|
|
CHECK_EQ(sorted[4], program->body.data[4]);
|
|
CHECK_EQ(sorted[5], program->body.data[5]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "dont_reorder_function_after_assignment_to_global")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local f
|
|
|
|
function g()
|
|
f()
|
|
end
|
|
|
|
f = function() end
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(3, sorted.size());
|
|
|
|
CHECK_EQ(sorted[0], program->body.data[0]);
|
|
CHECK_EQ(sorted[1], program->body.data[1]);
|
|
CHECK_EQ(sorted[2], program->body.data[2]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "local_functions_need_sorting_too")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local a = nil -- 0
|
|
|
|
local function f() -- 1 depends on 4
|
|
a.c = 4
|
|
end
|
|
|
|
local function g() -- 2 depends on 1
|
|
f()
|
|
end
|
|
|
|
a = {} -- 3
|
|
a.c = nil -- 4
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(5, sorted.size());
|
|
|
|
CHECK_EQ(sorted[0], program->body.data[0]);
|
|
CHECK_EQ(sorted[1], program->body.data[3]);
|
|
CHECK_EQ(sorted[2], program->body.data[4]);
|
|
CHECK_EQ(sorted[3], program->body.data[1]);
|
|
CHECK_EQ(sorted[4], program->body.data[2]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "dont_force_checking_until_an_AstExprCall_needs_the_symbol")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
function A(obj)
|
|
C(obj)
|
|
end
|
|
|
|
local B = A -- It would be an error to force checking of A at this point just because the definition of B is an imperative
|
|
|
|
function C(player)
|
|
end
|
|
|
|
local D = A(nil) -- The real dependency on A is here, where A is invoked.
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(4, sorted.size());
|
|
|
|
auto A = program->body.data[0];
|
|
auto B = program->body.data[1];
|
|
auto C = program->body.data[2];
|
|
auto D = program->body.data[3];
|
|
|
|
CHECK_EQ(sorted[0], C);
|
|
CHECK_EQ(sorted[1], A);
|
|
CHECK_EQ(sorted[2], B);
|
|
CHECK_EQ(sorted[3], D);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "dont_reorder_imperatives")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local temp = work
|
|
work = arr
|
|
arr = temp
|
|
width = width * 2
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(4, sorted.size());
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "sort_typealias_first")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
local foo: A = 1
|
|
type A = number
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(2, sorted.size());
|
|
|
|
auto A = program->body.data[0];
|
|
auto B = program->body.data[1];
|
|
|
|
CHECK_EQ(sorted[0], B);
|
|
CHECK_EQ(sorted[1], A);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "typealias_of_typeof_is_not_sorted")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
type Foo = typeof(foo)
|
|
local function foo(x: number) end
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(2, sorted.size());
|
|
|
|
auto A = program->body.data[0];
|
|
auto B = program->body.data[1];
|
|
|
|
CHECK_EQ(sorted[0], A);
|
|
CHECK_EQ(sorted[1], B);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "nested_type_annotations_depends_on_later_typealiases")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
type Foo = A | B
|
|
type B = number
|
|
type A = string
|
|
)");
|
|
|
|
auto sorted = toposort(*program);
|
|
|
|
REQUIRE_EQ(3, sorted.size());
|
|
|
|
auto Foo = program->body.data[0];
|
|
auto B = program->body.data[1];
|
|
auto A = program->body.data[2];
|
|
|
|
CHECK_EQ(sorted[0], B);
|
|
CHECK_EQ(sorted[1], A);
|
|
CHECK_EQ(sorted[2], Foo);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "return_comes_last")
|
|
{
|
|
CheckResult result = check(R"(
|
|
export type Module = { bar: (number) -> boolean, foo: () -> string }
|
|
|
|
return function() : Module
|
|
local module = {}
|
|
|
|
local function confuseCompiler() return module.foo() end
|
|
|
|
module.foo = function() return "" end
|
|
|
|
function module.bar(x:number)
|
|
confuseCompiler()
|
|
return true
|
|
end
|
|
|
|
return module
|
|
end
|
|
)");
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "break_comes_last")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
repeat
|
|
local module = {}
|
|
local function confuseCompiler() return module.foo() end
|
|
module.foo = function() return "" end
|
|
break
|
|
until true
|
|
)");
|
|
|
|
REQUIRE(program->body.size == 1);
|
|
|
|
auto repeat = program->body.data[0]->as<AstStatRepeat>();
|
|
REQUIRE(repeat);
|
|
|
|
REQUIRE(repeat->body->body.size == 4);
|
|
|
|
auto sorted = toposort(*repeat->body);
|
|
|
|
REQUIRE(sorted.size() == 4);
|
|
CHECK_EQ(sorted[3], repeat->body->body.data[3]);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(Fixture, "continue_comes_last")
|
|
{
|
|
AstStatBlock* program = parse(R"(
|
|
repeat
|
|
local module = {}
|
|
local function confuseCompiler() return module.foo() end
|
|
module.foo = function() return "" end
|
|
continue
|
|
until true
|
|
)");
|
|
|
|
REQUIRE(program->body.size == 1);
|
|
|
|
auto repeat = program->body.data[0]->as<AstStatRepeat>();
|
|
REQUIRE(repeat);
|
|
|
|
REQUIRE(repeat->body->body.size == 4);
|
|
|
|
auto sorted = toposort(*repeat->body);
|
|
|
|
REQUIRE(sorted.size() == 4);
|
|
CHECK_EQ(sorted[3], repeat->body->body.data[3]);
|
|
}
|
|
|
|
TEST_SUITE_END();
|