mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
ce8495a69e
# What's Changed? - Code refactoring with a new clang-format - More bug fixes / test case fixes in the new solver ## New Solver - More precise telemetry collection of `any` types - Simplification of two completely disjoint tables combines them into a single table that inherits all properties / indexers - Refining a `never & <anything>` does not produce type family types nor constraints - Silence "inference failed to complete" error when it is the only error reported --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Dibri Nsofor <dnsofor@roblox.com> Co-authored-by: Jeremy Yoo <jyoo@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: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1763 lines
52 KiB
C++
1763 lines
52 KiB
C++
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
#include "Luau/Differ.h"
|
|
#include "Luau/Common.h"
|
|
#include "Luau/Error.h"
|
|
#include "Luau/Frontend.h"
|
|
|
|
#include "Fixture.h"
|
|
#include "ClassFixture.h"
|
|
|
|
#include "Luau/Symbol.h"
|
|
#include "Luau/Type.h"
|
|
#include "ScopedFlags.h"
|
|
#include "doctest.h"
|
|
#include <iostream>
|
|
|
|
using namespace Luau;
|
|
|
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
|
|
|
TEST_SUITE_BEGIN("Differ");
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_numbers")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = 5
|
|
local almostFoo = 78
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_strings")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = "hello"
|
|
local almostFoo = "world"
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_tables")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1, y = "where" }
|
|
local almostFoo = { x = 5, y = "when" }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "a_table_missing_property")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1, y = 2 }
|
|
local almostFoo = { x = 1, z = 3 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo is missing "
|
|
"the property y"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "left_table_missing_property")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1 }
|
|
local almostFoo = { x = 1, z = 3 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at foo is missing the property z, while the right type at almostFoo.z "
|
|
"has type number"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1, y = 2 }
|
|
local almostFoo = { x = 1, y = "two" }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at foo.y has type number, while the right type at almostFoo.y has type "
|
|
"string"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "a_table_wrong_type")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: string
|
|
local almostFoo: number
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type string, while the right type at "
|
|
"<unlabeled-symbol> has type number"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_type")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1, inner = { table = { has = { wrong = { value = 5 } } } } }
|
|
local almostFoo = { x = 1, inner = { table = { has = { wrong = { value = "five" } } } } }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.value has type number, while the right "
|
|
"type at almostFoo.inner.table.has.wrong.value has type string"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "a_nested_table_wrong_match")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = { x = 1, inner = { table = { has = { wrong = { variant = { because = { it = { goes = { on = "five" } } } } } } } } }
|
|
local almostFoo = { x = 1, inner = { table = { has = { wrong = { variant = "five" } } } } }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
"DiffError: these two types are not equal because the left type at foo.inner.table.has.wrong.variant has type { because: { it: { goes: "
|
|
"{ on: string } } } }, while the right type at almostFoo.inner.table.has.wrong.variant has type string"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_missing_property")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = foo
|
|
local almostFoo = { x = 2 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo is missing the property foo)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "left_cyclic_table_right_table_property_wrong")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = foo
|
|
local almostFoo = { foo = 2 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 }, while the right type at almostFoo.foo has type number)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_missing_property")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = foo
|
|
local almostFoo = { x = 2 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"almostFoo",
|
|
"foo",
|
|
R"(DiffError: these two types are not equal because the left type at almostFoo.x has type number, while the right type at <unlabeled-symbol> is missing the property x)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = foo
|
|
local almostFoo = { foo = 2 }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"almostFoo",
|
|
"foo",
|
|
R"(DiffError: these two types are not equal because the left type at almostFoo.foo has type number, while the right type at <unlabeled-symbol>.foo has type t1 where t1 = { foo: t1 })"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
|
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = foo
|
|
local almostFoo = id({})
|
|
almostFoo.foo = almostFoo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_shifted_circles_are_not_different")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = id({})
|
|
foo.foo.foo = id({})
|
|
foo.foo.foo.foo = id({})
|
|
foo.foo.foo.foo.foo = foo
|
|
|
|
local builder = id({})
|
|
builder.foo = id({})
|
|
builder.foo.foo = id({})
|
|
builder.foo.foo.foo = id({})
|
|
builder.foo.foo.foo.foo = builder
|
|
-- Shift
|
|
local almostFoo = builder.foo.foo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "table_left_circle_right_measuring_tape")
|
|
{
|
|
// Left is a circle, right is a measuring tape
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = id({})
|
|
foo.foo.foo = id({})
|
|
foo.foo.foo.foo = id({})
|
|
foo.foo.foo.bar = id({}) -- anchor to pin shape
|
|
foo.foo.foo.foo.foo = foo
|
|
local almostFoo = id({})
|
|
almostFoo.foo = id({})
|
|
almostFoo.foo.foo = id({})
|
|
almostFoo.foo.foo.foo = id({})
|
|
almostFoo.foo.foo.bar = id({}) -- anchor to pin shape
|
|
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.foo.foo.foo.foo.foo is missing the property bar, while the right type at <unlabeled-symbol>.foo.foo.foo.foo.foo.bar has type { })"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_measuring_tapes")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = id({})
|
|
foo.foo.foo = id({})
|
|
foo.foo.foo.foo = id({})
|
|
foo.foo.foo.foo.foo = foo.foo
|
|
local almostFoo = id({})
|
|
almostFoo.foo = id({})
|
|
almostFoo.foo.foo = id({})
|
|
almostFoo.foo.foo.foo = id({})
|
|
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_A_B_C")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Remove name from cyclic table
|
|
local foo = id({})
|
|
foo.foo = id({})
|
|
foo.foo.foo = id({})
|
|
foo.foo.foo.foo = id({})
|
|
foo.foo.foo.foo.foo = foo.foo
|
|
local almostFoo = id({})
|
|
almostFoo.foo = id({})
|
|
almostFoo.foo.foo = id({})
|
|
almostFoo.foo.foo.foo = id({})
|
|
almostFoo.foo.foo.foo.foo = almostFoo.foo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_A")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- Remove name from cyclic table
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
local foo = id({})
|
|
foo.left = id({})
|
|
foo.right = id({})
|
|
foo.left.left = id({})
|
|
foo.left.right = id({})
|
|
foo.right.left = id({})
|
|
foo.right.right = id({})
|
|
foo.right.left.left = id({})
|
|
foo.right.left.right = id({})
|
|
|
|
foo.right.left.left.child = foo.right
|
|
|
|
local almostFoo = id({})
|
|
almostFoo.left = id({})
|
|
almostFoo.right = id({})
|
|
almostFoo.left.left = id({})
|
|
almostFoo.left.right = id({})
|
|
almostFoo.right.left = id({})
|
|
almostFoo.right.right = id({})
|
|
almostFoo.right.left.left = id({})
|
|
almostFoo.right.left.right = id({})
|
|
|
|
almostFoo.right.left.left.child = almostFoo.right
|
|
|
|
-- Bindings for requireType
|
|
local fooLeft = foo.left
|
|
local fooRight = foo.left.right
|
|
local fooLeftLeft = foo.left.left
|
|
local fooLeftRight = foo.left.right
|
|
local fooRightLeft = foo.right.left
|
|
local fooRightRight = foo.right.right
|
|
local fooRightLeftLeft = foo.right.left.left
|
|
local fooRightLeftRight = foo.right.left.right
|
|
|
|
local almostFooLeft = almostFoo.left
|
|
local almostFooRight = almostFoo.left.right
|
|
local almostFooLeftLeft = almostFoo.left.left
|
|
local almostFooLeftRight = almostFoo.left.right
|
|
local almostFooRightLeft = almostFoo.right.left
|
|
local almostFooRightRight = almostFoo.right.right
|
|
local almostFooRightLeftLeft = almostFoo.right.left.left
|
|
local almostFooRightLeftRight = almostFoo.right.left.right
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_B")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- Remove name from cyclic table
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
local foo = id({})
|
|
foo.left = id({})
|
|
foo.right = id({})
|
|
foo.left.left = id({})
|
|
foo.left.right = id({})
|
|
foo.right.left = id({})
|
|
foo.right.right = id({})
|
|
foo.right.left.left = id({})
|
|
foo.right.left.right = id({})
|
|
|
|
foo.right.left.left.child = foo.left
|
|
|
|
local almostFoo = id({})
|
|
almostFoo.left = id({})
|
|
almostFoo.right = id({})
|
|
almostFoo.left.left = id({})
|
|
almostFoo.left.right = id({})
|
|
almostFoo.right.left = id({})
|
|
almostFoo.right.right = id({})
|
|
almostFoo.right.left.left = id({})
|
|
almostFoo.right.left.right = id({})
|
|
|
|
almostFoo.right.left.left.child = almostFoo.left
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_C")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- Remove name from cyclic table
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
local foo = id({})
|
|
foo.left = id({})
|
|
foo.right = id({})
|
|
foo.left.left = id({})
|
|
foo.left.right = id({})
|
|
foo.right.left = id({})
|
|
foo.right.right = id({})
|
|
foo.right.left.left = id({})
|
|
foo.right.left.right = id({})
|
|
|
|
foo.right.left.left.child = foo
|
|
|
|
local almostFoo = id({})
|
|
almostFoo.left = id({})
|
|
almostFoo.right = id({})
|
|
almostFoo.left.left = id({})
|
|
almostFoo.left.right = id({})
|
|
almostFoo.right.left = id({})
|
|
almostFoo.right.right = id({})
|
|
almostFoo.right.left.left = id({})
|
|
almostFoo.right.left.right = id({})
|
|
|
|
almostFoo.right.left.left.child = almostFoo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_kind_D")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- Remove name from cyclic table
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
local foo = id({})
|
|
foo.left = id({})
|
|
foo.right = id({})
|
|
foo.left.left = id({})
|
|
foo.left.right = id({})
|
|
foo.right.left = id({})
|
|
foo.right.right = id({})
|
|
foo.right.left.left = id({})
|
|
foo.right.left.right = id({})
|
|
|
|
foo.right.left.left.child = foo.right.left.left
|
|
|
|
local almostFoo = id({})
|
|
almostFoo.left = id({})
|
|
almostFoo.right = id({})
|
|
almostFoo.left.left = id({})
|
|
almostFoo.left.right = id({})
|
|
almostFoo.right.left = id({})
|
|
almostFoo.right.right = id({})
|
|
almostFoo.right.left.left = id({})
|
|
almostFoo.right.left.right = id({})
|
|
|
|
almostFoo.right.left.left.child = almostFoo.right.left.left
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_cyclic_diamonds_unraveled")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- Remove name from cyclic table
|
|
local function id<a>(x: a): a
|
|
return x
|
|
end
|
|
|
|
-- Pattern 1
|
|
local foo = id({})
|
|
foo.child = id({})
|
|
foo.child.left = id({})
|
|
foo.child.right = id({})
|
|
|
|
foo.child.left.child = foo
|
|
foo.child.right.child = foo
|
|
|
|
-- Pattern 2
|
|
local almostFoo = id({})
|
|
almostFoo.child = id({})
|
|
almostFoo.child.left = id({})
|
|
almostFoo.child.right = id({})
|
|
|
|
almostFoo.child.left.child = id({}) -- Use a new table
|
|
almostFoo.child.right.child = almostFoo.child.left.child -- Refer to the same new table
|
|
|
|
almostFoo.child.left.child.child = id({})
|
|
almostFoo.child.left.child.child.left = id({})
|
|
almostFoo.child.left.child.child.right = id({})
|
|
|
|
almostFoo.child.left.child.child.left.child = almostFoo.child.left.child
|
|
almostFoo.child.left.child.child.right.child = almostFoo.child.left.child
|
|
|
|
-- Pattern 3
|
|
local anotherFoo = id({})
|
|
anotherFoo.child = id({})
|
|
anotherFoo.child.left = id({})
|
|
anotherFoo.child.right = id({})
|
|
|
|
anotherFoo.child.left.child = id({}) -- Use a new table
|
|
anotherFoo.child.right.child = id({}) -- Use another new table
|
|
|
|
anotherFoo.child.left.child.child = id({})
|
|
anotherFoo.child.left.child.child.left = id({})
|
|
anotherFoo.child.left.child.child.right = id({})
|
|
anotherFoo.child.right.child.child = id({})
|
|
anotherFoo.child.right.child.child.left = id({})
|
|
anotherFoo.child.right.child.child.right = id({})
|
|
|
|
anotherFoo.child.left.child.child.left.child = anotherFoo.child.left.child
|
|
anotherFoo.child.left.child.child.right.child = anotherFoo.child.left.child
|
|
anotherFoo.child.right.child.child.left.child = anotherFoo.child.right.child
|
|
anotherFoo.child.right.child.child.right.child = anotherFoo.child.right.child
|
|
|
|
-- Pattern 4
|
|
local cleverFoo = id({})
|
|
cleverFoo.child = id({})
|
|
cleverFoo.child.left = id({})
|
|
cleverFoo.child.right = id({})
|
|
|
|
cleverFoo.child.left.child = id({}) -- Use a new table
|
|
cleverFoo.child.right.child = id({}) -- Use another new table
|
|
|
|
cleverFoo.child.left.child.child = id({})
|
|
cleverFoo.child.left.child.child.left = id({})
|
|
cleverFoo.child.left.child.child.right = id({})
|
|
cleverFoo.child.right.child.child = id({})
|
|
cleverFoo.child.right.child.child.left = id({})
|
|
cleverFoo.child.right.child.child.right = id({})
|
|
-- Same as pattern 3, but swapped here
|
|
cleverFoo.child.left.child.child.left.child = cleverFoo.child.right.child -- Swap
|
|
cleverFoo.child.left.child.child.right.child = cleverFoo.child.right.child
|
|
cleverFoo.child.right.child.child.left.child = cleverFoo.child.left.child
|
|
cleverFoo.child.right.child.child.right.child = cleverFoo.child.left.child
|
|
|
|
-- Pattern 5
|
|
local cheekyFoo = id({})
|
|
cheekyFoo.child = id({})
|
|
cheekyFoo.child.left = id({})
|
|
cheekyFoo.child.right = id({})
|
|
|
|
cheekyFoo.child.left.child = foo -- Use existing pattern
|
|
cheekyFoo.child.right.child = foo -- Use existing pattern
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
std::vector<std::string> symbols{"foo", "almostFoo", "anotherFoo", "cleverFoo", "cheekyFoo"};
|
|
|
|
for (auto left : symbols)
|
|
{
|
|
for (auto right : symbols)
|
|
{
|
|
compareTypesEq(left, right);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function_cyclic")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo()
|
|
return foo
|
|
end
|
|
function almostFoo()
|
|
function bar()
|
|
return bar
|
|
end
|
|
return bar
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function_table_cyclic")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo()
|
|
return {
|
|
bar = foo
|
|
}
|
|
end
|
|
function almostFoo()
|
|
function bar()
|
|
return {
|
|
bar = bar
|
|
}
|
|
end
|
|
return {
|
|
bar = bar
|
|
}
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_table_self_referential_cyclic")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
// ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo()
|
|
return {
|
|
bar = foo
|
|
}
|
|
end
|
|
function almostFoo()
|
|
function bar()
|
|
return bar
|
|
end
|
|
return {
|
|
bar = bar
|
|
}
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = { bar: () -> t1 }, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)"
|
|
);
|
|
else
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = {| bar: () -> t1 |}, while the right type at <unlabeled-symbol>.Ret[1].bar.Ret[1] has type t1 where t1 = () -> t1)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_union_cyclic")
|
|
{
|
|
TypeArena arena;
|
|
TypeId number = arena.addType(PrimitiveType{PrimitiveType::Number});
|
|
TypeId string = arena.addType(PrimitiveType{PrimitiveType::String});
|
|
|
|
TypeId foo = arena.addType(UnionType{std::vector<TypeId>{number, string}});
|
|
UnionType* unionFoo = getMutable<UnionType>(foo);
|
|
unionFoo->options.push_back(foo);
|
|
|
|
TypeId almostFoo = arena.addType(UnionType{std::vector<TypeId>{number, string}});
|
|
UnionType* unionAlmostFoo = getMutable<UnionType>(almostFoo);
|
|
unionAlmostFoo->options.push_back(almostFoo);
|
|
|
|
compareEq(foo, almostFoo);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_intersection_cyclic")
|
|
{
|
|
// Old solver does not correctly refine test types
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo1(x: number)
|
|
return x
|
|
end
|
|
function foo2(x: string)
|
|
return 0
|
|
end
|
|
function bar1(x: number)
|
|
return x
|
|
end
|
|
function bar2(x: string)
|
|
return 0
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
TypeId foo1 = requireType("foo1");
|
|
TypeId foo2 = requireType("foo2");
|
|
TypeId bar1 = requireType("bar1");
|
|
TypeId bar2 = requireType("bar2");
|
|
|
|
TypeArena arena;
|
|
|
|
TypeId foo = arena.addType(IntersectionType{std::vector<TypeId>{foo1, foo2}});
|
|
IntersectionType* intersectionFoo = getMutable<IntersectionType>(foo);
|
|
intersectionFoo->parts.push_back(foo);
|
|
|
|
TypeId almostFoo = arena.addType(IntersectionType{std::vector<TypeId>{bar1, bar2}});
|
|
IntersectionType* intersectionAlmostFoo = getMutable<IntersectionType>(almostFoo);
|
|
intersectionAlmostFoo->parts.push_back(almostFoo);
|
|
|
|
compareEq(foo, almostFoo);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "singleton")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: "hello" = "hello"
|
|
local almostFoo: true = true
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type "hello", while the right type at <unlabeled-symbol> has type true)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_singleton")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: "hello" = "hello"
|
|
local almostFoo: "hello"
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "singleton_string")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: "hello" = "hello"
|
|
local almostFoo: "world" = "world"
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type "hello", while the right type at <unlabeled-symbol> has type "world")"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation")
|
|
{
|
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
|
return;
|
|
|
|
CheckResult result = check(R"(
|
|
local bar: { x: { y: unknown }}
|
|
local almostBar: { x: { y: unknown }}
|
|
|
|
local foo
|
|
local almostFoo
|
|
|
|
if typeof(bar.x.y) ~= "string" then
|
|
foo = bar
|
|
end
|
|
|
|
if typeof(almostBar.x.y) ~= "number" then
|
|
almostFoo = almostBar
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { x: { y: ~string } }, while the right type at <unlabeled-symbol> is a union missing type { x: { y: ~string } })"
|
|
);
|
|
|
|
// TODO: a more desirable expected error here is as below, but `Differ` requires improvements to
|
|
// dealing with unions to get something like this (recognizing that the union is identical
|
|
// except in one component where they differ).
|
|
//
|
|
// compareTypesNe("foo", "almostFoo",
|
|
// R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.x.y.Negation has type string, while the right type
|
|
// at <unlabeled-symbol>.x.y.Negation has type number)");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "union_missing_right")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: string | number
|
|
local almostFoo: boolean | string
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type number, while the right type at <unlabeled-symbol> is a union missing type number)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "union_missing_left")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: string | number
|
|
local almostFoo: boolean | string | number
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union missing type boolean, while the right type at <unlabeled-symbol> is a union containing type boolean)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "union_missing")
|
|
{
|
|
// TODO: this test case produces an error message that is not the most UX-friendly
|
|
|
|
CheckResult result = check(R"(
|
|
local foo: { bar: number, pan: string } | { baz: boolean, rot: "singleton" }
|
|
local almostFoo: { bar: number, pan: string } | { baz: string, rot: "singleton" }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type { baz: boolean, rot: "singleton" }, while the right type at <unlabeled-symbol> is a union missing type { baz: boolean, rot: "singleton" })"
|
|
);
|
|
else
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is a union containing type {| baz: boolean, rot: "singleton" |}, while the right type at <unlabeled-symbol> is a union missing type {| baz: boolean, rot: "singleton" |})"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_right")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: (number) -> () & (string) -> ()
|
|
local almostFoo: (string) -> () & (boolean) -> ()
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type (number) -> (), while the right type at <unlabeled-symbol> is an intersection missing type (number) -> ())"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_missing_left")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: (number) -> () & (string) -> ()
|
|
local almostFoo: (string) -> () & (boolean) -> () & (number) -> ()
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type (boolean) -> (), while the right type at <unlabeled-symbol> is an intersection containing type (boolean) -> ())"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_right")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: { x: number } & { y: string }
|
|
local almostFoo: { y: string } & { z: boolean }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type { x: number }, while the right type at <unlabeled-symbol> is an intersection missing type { x: number })"
|
|
);
|
|
else
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection containing type {| x: number |}, while the right type at <unlabeled-symbol> is an intersection missing type {| x: number |})"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "intersection_tables_missing_left")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: { x: number } & { y: string }
|
|
local almostFoo: { y: string } & { z: boolean } & { x: number }
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type { z: boolean }, while the right type at <unlabeled-symbol> is an intersection containing type { z: boolean })"
|
|
);
|
|
else
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> is an intersection missing type {| z: boolean |}, while the right type at <unlabeled-symbol> is an intersection containing type {| z: boolean |})"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number)
|
|
return x
|
|
end
|
|
function almostFoo(y: number)
|
|
return y + 10
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function bar(x: number, y: string)
|
|
return x, y
|
|
end
|
|
function almostBar(a: number, b: string)
|
|
return a, b
|
|
end
|
|
function foo(x: number, y: string, z: boolean)
|
|
return z, bar(x, y)
|
|
end
|
|
function almostFoo(a: number, b: string, c: boolean)
|
|
return c, almostBar(a, b)
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_function_inferred_ret_length_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function bar(x: number, y: string)
|
|
return x, y
|
|
end
|
|
function foo(x: number, y: string, z: boolean)
|
|
return bar(x, y), z
|
|
end
|
|
function almostFoo(a: number, b: string, c: boolean)
|
|
return a, c
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: number, z: number)
|
|
return x * y * z
|
|
end
|
|
function almostFoo(a: number, b: number, msg: string)
|
|
return a
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Arg[3] has type number, while the right type at <unlabeled-symbol>.Arg[3] has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_normal_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: number, z: string)
|
|
return x * y
|
|
end
|
|
function almostFoo(a: number, y: string, msg: string)
|
|
return a
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Arg[2] has type number, while the right type at <unlabeled-symbol>.Arg[2] has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_ret_normal")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: number, z: string)
|
|
return x
|
|
end
|
|
function almostFoo(a: number, b: number, msg: string)
|
|
return msg
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Ret[1] has type number, while the right type at <unlabeled-symbol>.Ret[1] has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: number)
|
|
return x
|
|
end
|
|
function almostFoo(x: number, y: number, c: number)
|
|
return x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 2 or more arguments, while the right type at <unlabeled-symbol> takes 3 or more arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string, z: number)
|
|
return z
|
|
end
|
|
function almostFoo(x: number, y: string)
|
|
return x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 3 or more arguments, while the right type at <unlabeled-symbol> takes 2 or more arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo()
|
|
return 5
|
|
end
|
|
function almostFoo(x: number, y: string)
|
|
return x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 0 or more arguments, while the right type at <unlabeled-symbol> takes 2 or more arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_arg_length_none_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number)
|
|
return x
|
|
end
|
|
function almostFoo()
|
|
return 5
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 1 or more arguments, while the right type at <unlabeled-symbol> takes 0 or more arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: number)
|
|
return x
|
|
end
|
|
function almostFoo(x: number, y: number)
|
|
return x, y
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> returns 1 values, while the right type at <unlabeled-symbol> returns 2 values)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string, z: number)
|
|
return y, x, z
|
|
end
|
|
function almostFoo(x: number, y: string, z: number)
|
|
return y, x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> returns 3 values, while the right type at <unlabeled-symbol> returns 2 values)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string)
|
|
return
|
|
end
|
|
function almostFoo(x: number, y: string)
|
|
return x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> returns 0 values, while the right type at <unlabeled-symbol> returns 1 values)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_ret_length_none_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo()
|
|
return 5
|
|
end
|
|
function almostFoo()
|
|
return
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> returns 1 values, while the right type at <unlabeled-symbol> returns 0 values)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_normal")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string, ...: number)
|
|
return x, y
|
|
end
|
|
function almostFoo(a: number, b: string, ...: string)
|
|
return a, b
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Arg[Variadic] has type number, while the right type at <unlabeled-symbol>.Arg[Variadic] has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string, ...: number)
|
|
return x, y
|
|
end
|
|
function almostFoo(a: number, b: string)
|
|
return a, b
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Arg[Variadic] has type number, while the right type at <unlabeled-symbol>.Arg[Variadic] has type any)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_arg_missing_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x: number, y: string)
|
|
return x, y
|
|
end
|
|
function almostFoo(a: number, b: string, ...: string)
|
|
return a, b
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.Arg[Variadic] has type any, while the right type at <unlabeled-symbol>.Arg[Variadic] has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
-- allowed to be oversaturated
|
|
function foo(x: number, y: string)
|
|
return x, y
|
|
end
|
|
-- must not be oversaturated
|
|
local almostFoo: (number, string) -> (number, string) = foo
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 2 or more arguments, while the right type at <unlabeled-symbol> takes 2 arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "function_variadic_oversaturation_2")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
-- must not be oversaturated
|
|
local foo: (number, string) -> (number, string)
|
|
-- allowed to be oversaturated
|
|
function almostFoo(x: number, y: string)
|
|
return x, y
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> takes 2 arguments, while the right type at <unlabeled-symbol> takes 2 or more arguments)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generic")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo(x, y)
|
|
return x, y
|
|
end
|
|
function almostFoo(x, y)
|
|
return y, x
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Ret[1] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Ret[1])"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generic_one_vs_two")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo<X>(x: X, y: X)
|
|
return
|
|
end
|
|
function almostFoo<T, U>(x: T, y: U)
|
|
return
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[2] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[2])"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generic_three_or_three")
|
|
{
|
|
// Old solver does not correctly infer function typepacks
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
|
|
|
CheckResult result = check(R"(
|
|
function foo<X, Y>(x: X, y: X, z: Y)
|
|
return
|
|
end
|
|
function almostFoo<T, U>(x: T, y: U, z: U)
|
|
return
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[2] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[2])"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "equal_metatable")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local metaFoo = {
|
|
metaBar = 5
|
|
}
|
|
local metaAlmostFoo = {
|
|
metaBar = 1
|
|
}
|
|
local foo = {
|
|
bar = 3
|
|
}
|
|
setmetatable(foo, metaFoo)
|
|
local almostFoo = {
|
|
bar = 4
|
|
}
|
|
setmetatable(almostFoo, metaAlmostFoo)
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_normal")
|
|
{
|
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
|
|
|
CheckResult result = check(R"(
|
|
local metaFoo = {
|
|
metaBar = 5
|
|
}
|
|
local metaAlmostFoo = {
|
|
metaBar = 1
|
|
}
|
|
local foo = {
|
|
bar = 3
|
|
}
|
|
setmetatable(foo, metaFoo)
|
|
local almostFoo = {
|
|
bar = "hello"
|
|
}
|
|
setmetatable(almostFoo, metaAlmostFoo)
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.bar has type number, while the right type at <unlabeled-symbol>.bar has type string)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metanormal")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local metaFoo = {
|
|
metaBar = "world"
|
|
}
|
|
local metaAlmostFoo = {
|
|
metaBar = 1
|
|
}
|
|
local foo = {
|
|
bar = "amazing"
|
|
}
|
|
setmetatable(foo, metaFoo)
|
|
local almostFoo = {
|
|
bar = "hello"
|
|
}
|
|
setmetatable(almostFoo, metaAlmostFoo)
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable.metaBar has type string, while the right type at <unlabeled-symbol>.__metatable.metaBar has type number)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_left")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local metaFoo = {
|
|
metaBar = "world"
|
|
}
|
|
local metaAlmostFoo = {
|
|
metaBar = 1,
|
|
thisIsOnlyInRight = 2,
|
|
}
|
|
local foo = {
|
|
bar = "amazing"
|
|
}
|
|
setmetatable(foo, metaFoo)
|
|
local almostFoo = {
|
|
bar = "hello"
|
|
}
|
|
setmetatable(almostFoo, metaAlmostFoo)
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable is missing the property thisIsOnlyInRight, while the right type at <unlabeled-symbol>.__metatable.thisIsOnlyInRight has type number)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local metaFoo = {
|
|
metaBar = "world",
|
|
thisIsOnlyInLeft = 2,
|
|
}
|
|
local metaAlmostFoo = {
|
|
metaBar = 1,
|
|
}
|
|
local foo = {
|
|
bar = "amazing"
|
|
}
|
|
setmetatable(foo, metaFoo)
|
|
local almostFoo = {
|
|
bar = "hello"
|
|
}
|
|
setmetatable(almostFoo, metaAlmostFoo)
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol>.__metatable.thisIsOnlyInLeft has type number, while the right type at <unlabeled-symbol>.__metatable is missing the property thisIsOnlyInLeft)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "equal_class")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = BaseClass
|
|
local almostFoo = BaseClass
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixtureGeneric<ClassFixture>, "class_normal")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = BaseClass
|
|
local almostFoo = ChildClass
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type BaseClass, while the right type at <unlabeled-symbol> has type ChildClass)"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_generictp")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: <T...>() -> T...
|
|
local almostFoo: <U...>() -> U...
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generictp_ne_fn")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo: <T, U...>(...T) -> U...
|
|
local almostFoo: <U...>(U...) -> U...
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at <unlabeled-symbol> has type <T, U...>(...T) -> (U...), while the right type at <unlabeled-symbol> has type <U...>(U...) -> (U...))"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generictp_normal")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- trN should be X... -> Y...
|
|
-- s should be X -> Y...
|
|
-- x should be X
|
|
-- bij should be X... -> X...
|
|
|
|
-- Intended signature: <X..., Y..., Z>(X... -> Y..., Z -> X..., X... -> Y..., Z, Y... -> Y...) -> ()
|
|
function foo(tr, s, tr2, x, bij)
|
|
bij(bij(tr(s(x))))
|
|
bij(bij(tr2(s(x))))
|
|
end
|
|
-- Intended signature: <X..., Y..., Z>(X... -> X..., Z -> X..., X... -> Y..., Z, Y... -> Y...) -> ()
|
|
function almostFoo(bij, s, tr, x, bij2)
|
|
bij(bij(s(x)))
|
|
bij2(bij2(tr(s(x))))
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
INFO(Luau::toString(requireType("foo")));
|
|
INFO(Luau::toString(requireType("almostFoo")));
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[1].Ret[Variadic] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[1].Ret[Variadic])"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "generictp_normal_2")
|
|
{
|
|
CheckResult result = check(R"(
|
|
-- trN should be X... -> Y...
|
|
-- s should be X -> Y...
|
|
-- x should be X
|
|
-- bij should be X... -> X...
|
|
|
|
function foo(s, tr, tr2, x, bij)
|
|
bij(bij(tr(s(x))))
|
|
bij(bij(tr2(s(x))))
|
|
end
|
|
function almostFoo(s, bij, tr, x, bij2)
|
|
bij2(bij2(bij(bij(tr(s(x))))))
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
INFO(Luau::toString(requireType("foo")));
|
|
INFO(Luau::toString(requireType("almostFoo")));
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left generic at <unlabeled-symbol>.Arg[2].Arg[Variadic] cannot be the same type parameter as the right generic at <unlabeled-symbol>.Arg[2].Arg[Variadic])"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "equal_generictp_cyclic")
|
|
{
|
|
CheckResult result = check(R"(
|
|
function foo(f, g, s, x)
|
|
f(f(g(g(s(x)))))
|
|
return foo
|
|
end
|
|
function almostFoo(f, g, s, x)
|
|
g(g(f(f(s(x)))))
|
|
return almostFoo
|
|
end
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
INFO(Luau::toString(requireType("foo")));
|
|
INFO(Luau::toString(requireType("almostFoo")));
|
|
|
|
compareTypesEq("foo", "almostFoo");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "symbol_forward")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = 5
|
|
local almostFoo = "five"
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
INFO(Luau::toString(requireType("foo")));
|
|
INFO(Luau::toString(requireType("almostFoo")));
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at foo has type number, while the right type at almostFoo has type string)",
|
|
true
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(DifferFixture, "newlines")
|
|
{
|
|
CheckResult result = check(R"(
|
|
local foo = 5
|
|
local almostFoo = "five"
|
|
)");
|
|
LUAU_REQUIRE_NO_ERRORS(result);
|
|
|
|
INFO(Luau::toString(requireType("foo")));
|
|
INFO(Luau::toString(requireType("almostFoo")));
|
|
|
|
compareTypesNe(
|
|
"foo",
|
|
"almostFoo",
|
|
R"(DiffError: these two types are not equal because the left type at
|
|
foo
|
|
has type
|
|
number,
|
|
while the right type at
|
|
almostFoo
|
|
has type
|
|
string)",
|
|
true,
|
|
true
|
|
);
|
|
}
|
|
|
|
TEST_SUITE_END();
|