mirror of
https://github.com/luau-lang/luau.git
synced 2025-03-05 11:41:40 +00:00

Hey folks, another week means another Luau release! This one features a number of bug fixes in the New Type Solver including improvements to user-defined type functions and a bunch of work to untangle some of the outstanding issues we've been seeing with constraint solving not completing in real world use. We're also continuing to make progress on crashes and other problems that affect the stability of fragment autocomplete, as we work towards delivering consistent, low-latency autocomplete for any editor environment. ## New Type Solver - Fix a bug in user-defined type functions where `print` would incorrectly insert `\1` a number of times. - Fix a bug where attempting to refine an optional generic with a type test will cause a false positive type error (fixes #1666) - Fix a bug where the `refine` type family would not skip over `*no-refine*` discriminants (partial resolution for #1424) - Fix a constraint solving bug where recursive function calls would consistently produce cyclic constraints leading to incomplete or inaccurate type inference. - Implement `readparent` and `writeparent` for class types in user-defined type functions, replacing the incorrectly included `parent` method. - Add initial groundwork (under a debug flag) for eager free type generalization, moving us towards further improvements to constraint solving incomplete errors. ## Fragment Autocomplete - Ease up some assertions to improve stability of mixed-mode use of the two type solvers (i.e. using Fragment Autocomplete on a type graph originally produced by the old type solver) - Resolve a bug with type compatibility checks causing internal compiler errors in autocomplete. ## Lexer and Parser - Improve the accuracy of the roundtrippable AST parsing mode by correctly placing closing parentheses on type groupings. - Add a getter for `offset` in the Lexer by @aduermael in #1688 - Add a second entry point to the parser to parse an expression, `parseExpr` ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: James McNellis <jmcnellis@roblox.com> Co-authored-by: Talha Pathan <tpathan@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood <ayoungblood@roblox.com> Co-authored-by: Menarul Alam <malam@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
1038 lines
31 KiB
C++
1038 lines
31 KiB
C++
// 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/RequireTracer.h"
|
|
|
|
#include "Fixture.h"
|
|
|
|
#include "ScopedFlags.h"
|
|
#include "doctest.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace Luau;
|
|
|
|
using Pattern = AnyTypeSummary::Pattern;
|
|
|
|
LUAU_FASTFLAG(LuauSolverV2)
|
|
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
|
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|
LUAU_FASTFLAG(StudioReportLuauAny2)
|
|
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
|
|
LUAU_FASTFLAG(LuauStoreCSTData)
|
|
LUAU_FASTFLAG(LuauAstTypeGroup2)
|
|
LUAU_FASTFLAG(LuauDeferBidirectionalInferenceForTableAssignment)
|
|
LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement)
|
|
|
|
|
|
struct ATSFixture : BuiltinsFixture
|
|
{
|
|
|
|
ATSFixture()
|
|
{
|
|
addGlobalBinding(frontend.globals, "game", builtinTypes->anyType, "@test");
|
|
addGlobalBinding(frontend.globals, "script", builtinTypes->anyType, "@test");
|
|
}
|
|
};
|
|
|
|
TEST_SUITE_BEGIN("AnyTypeSummaryTest");
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
type A = (number, string) -> ...any
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type A = (number, string)->( ...any)");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "export_alias")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
if (FFlag::LuauStoreCSTData && FFlag::LuauAstTypeGroup2)
|
|
{
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0...>( true | any)->(''))");
|
|
}
|
|
else if (FFlag::LuauStoreCSTData)
|
|
{
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0...>( true | any)->(''))");
|
|
}
|
|
else if (FFlag::LuauAstTypeGroup2)
|
|
{
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0& (<t0 ...>(true | any)->(''))");
|
|
}
|
|
else
|
|
{
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "export type t8<t8> = t0 &(<t0 ...>(true | any)->(''))");
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "typepacks")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function fallible(t: number): ...any
|
|
if t > 0 then
|
|
return true, t -- should catch this
|
|
end
|
|
return false, "must be positive" -- should catch this
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::TypePk);
|
|
LUAU_ASSERT(
|
|
module->ats.typeInfo[0].node ==
|
|
"local function fallible(t: number): ...any\n if t > 0 then\n return true, t\n end\n return false, 'must be positive'\nend"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "typepacks_no_ret")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
-- TODO: if partially typed, we'd want to know too
|
|
local function fallible(t: number)
|
|
if t > 0 then
|
|
return true, t
|
|
end
|
|
return false, "must be positive"
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen_table")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
type Pair<T> = {first: T, second: any}
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = {first: T, second: any}");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "assign_uneq")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/B"] = R"(
|
|
local function greetings(name: string)
|
|
return "Hello, " .. name, nil
|
|
end
|
|
|
|
local x, y = greetings("Dibri")
|
|
local x, y = greetings("Dibri"), nil
|
|
local x, y, z = greetings("Dibri") -- mismatch
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/B");
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/B");
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_typepack_any_gen")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
-- type Pair<T> = (boolean, string, ...any) -> {T} -- type aliases with generics/pack do not seem to be processed?
|
|
type Pair<T> = (boolean, T) -> ...any
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Pair<T> = (boolean, T)->( ...any)");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "typeof_any_in_func")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function f()
|
|
local a: any = 1
|
|
local b: typeof(a) = 1
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f()\n local a: any = 1\n local b: typeof(a) = 1\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "generic_types")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function foo<A>(a: (...A) -> any, ...: A)
|
|
return a(...)
|
|
end
|
|
|
|
local function addNumbers(num1, num2)
|
|
local result = num1 + num2
|
|
return result
|
|
end
|
|
|
|
foo(addNumbers)
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncApp);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function foo<A>(a: (...A)->( any),...: A)\n return a(...)\nend");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "no_annot")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local character = script.Parent
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "if_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
function f(x: any)
|
|
if not x then
|
|
x = {
|
|
y = math.random(0, 2^31-1),
|
|
left = nil,
|
|
right = nil
|
|
}
|
|
else
|
|
local expected = x * 5
|
|
end
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(
|
|
module->ats.typeInfo[0].node == "function f(x: any)\nif not x then\nx = {\n y = math.random(0, 2^31-1),\n left = nil,\n right = "
|
|
"nil\n}\nelse\n local expected = x * 5\nend\nend"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "variadic_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function f(): (number, ...any)
|
|
return 1, 5 --catching this
|
|
end
|
|
|
|
local x, y, z = f() -- not catching this any because no annot
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(): (number, ...any)\n return 1, 5\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "type_alias_intersection")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
type XCoord = {x: number}
|
|
type YCoord = {y: any}
|
|
type Vector2 = XCoord & YCoord -- table type intersections do not get normalized
|
|
local vec2: Vector2 = {x = 1, y = 2}
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[2].code == Pattern::VarAnnot);
|
|
LUAU_ASSERT(module->ats.typeInfo[2].node == "local vec2: Vector2 = {x = 1, y = 2}");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_func_arg")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function f(...: any)
|
|
end
|
|
local function f(x: number?, y, z: any)
|
|
end
|
|
function f(x: number?, y, z: any)
|
|
end
|
|
function f(...: any)
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 4);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_func_apps")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function f(...: any)
|
|
end
|
|
f("string", 123)
|
|
f("string")
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAny);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function f(...: any)\n end");
|
|
}
|
|
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local CAR_COLLISION_GROUP = "Car"
|
|
|
|
-- Set the car collision group
|
|
for _, descendant in carTemplate:GetDescendants() do
|
|
if descendant:IsA("BasePart") then
|
|
descendant.CollisionGroup = CAR_COLLISION_GROUP
|
|
end
|
|
end
|
|
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
if (FFlag::LuauSkipNoRefineDuringRefinement)
|
|
{
|
|
REQUIRE_EQ(module->ats.typeInfo.size(), 1);
|
|
CHECK_EQ(module->ats.typeInfo[0].code, Pattern::Assign);
|
|
CHECK_EQ(module->ats.typeInfo[0].node, "descendant.CollisionGroup = CAR_COLLISION_GROUP");
|
|
}
|
|
else
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "unknown_symbol")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local function manageRace(raceContainer: Model)
|
|
RaceManager.new(raceContainer)
|
|
end
|
|
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "racing_3_short")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
|
|
local CollectionService = game:GetService("CollectionService")
|
|
|
|
local RaceManager = require(script.RaceManager)
|
|
|
|
local RACE_TAG = "Race"
|
|
|
|
local function manageRace(raceContainer: Model)
|
|
RaceManager.new(raceContainer)
|
|
end
|
|
|
|
local function initialize()
|
|
CollectionService:GetInstanceAddedSignal(RACE_TAG):Connect(manageRace)
|
|
|
|
for _, raceContainer in CollectionService:GetTagged(RACE_TAG) do
|
|
manageRace(raceContainer)
|
|
end
|
|
end
|
|
|
|
initialize()
|
|
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(2, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 5);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local function manageRace(raceContainer: Model)\n RaceManager.new(raceContainer)\nend");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local PhysicsService = game:GetService("PhysicsService")
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
|
|
local safePlayerAdded = require(script.safePlayerAdded)
|
|
|
|
local CAR_COLLISION_GROUP = "Car"
|
|
local CHARACTER_COLLISION_GROUP = "Character"
|
|
|
|
local carTemplate = ReplicatedStorage.Car
|
|
|
|
local function onCharacterAdded(character: Model)
|
|
-- Set the collision group for any parts that are added to the character
|
|
character.DescendantAdded:Connect(function(descendant)
|
|
if descendant:IsA("BasePart") then
|
|
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
|
|
end
|
|
end)
|
|
|
|
-- Set the collision group for any parts currently in the character
|
|
for _, descendant in character:GetDescendants() do
|
|
if descendant:IsA("BasePart") then
|
|
descendant.CollisionGroup = CHARACTER_COLLISION_GROUP
|
|
end
|
|
end
|
|
end
|
|
|
|
local function onPlayerAdded(player: Player)
|
|
player.CharacterAdded:Connect(onCharacterAdded)
|
|
|
|
if player.Character then
|
|
onCharacterAdded(player.Character)
|
|
end
|
|
end
|
|
|
|
local function initialize()
|
|
-- Setup collision groups
|
|
PhysicsService:RegisterCollisionGroup(CAR_COLLISION_GROUP)
|
|
PhysicsService:RegisterCollisionGroup(CHARACTER_COLLISION_GROUP)
|
|
|
|
-- Stop the collision groups from colliding with each other
|
|
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CAR_COLLISION_GROUP, false)
|
|
PhysicsService:CollisionGroupSetCollidable(CHARACTER_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
|
|
PhysicsService:CollisionGroupSetCollidable(CAR_COLLISION_GROUP, CHARACTER_COLLISION_GROUP, false)
|
|
|
|
-- Set the car collision group
|
|
for _, descendant in carTemplate:GetDescendants() do
|
|
if descendant:IsA("BasePart") then
|
|
descendant.CollisionGroup = CAR_COLLISION_GROUP
|
|
end
|
|
end
|
|
|
|
-- Set character collision groups for all players
|
|
safePlayerAdded(onPlayerAdded)
|
|
end
|
|
|
|
initialize()
|
|
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(3, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
if (FFlag::LuauSkipNoRefineDuringRefinement)
|
|
CHECK_EQ(module->ats.typeInfo.size(), 12);
|
|
else
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 11);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
if (FFlag::LuauStoreCSTData)
|
|
{
|
|
CHECK_EQ(
|
|
module->ats.typeInfo[0].node,
|
|
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
|
"descendant:IsA('BasePart') then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
|
"character:GetDescendants() do\n if descendant:IsA('BasePart') then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
|
"end\nend"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
LUAU_ASSERT(
|
|
module->ats.typeInfo[0].node ==
|
|
"local function onCharacterAdded(character: Model)\n\n character.DescendantAdded:Connect(function(descendant)\n if "
|
|
"descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n end)\n\n\n for _, descendant in "
|
|
"character:GetDescendants()do\n if descendant:IsA('BasePart')then\n descendant.CollisionGroup = CHARACTER_COLLISION_GROUP\n end\n "
|
|
"end\nend"
|
|
);
|
|
}
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "racing_spawning_1")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
// Previously we'd report an error because number <: 'a is not a
|
|
// supertype.
|
|
{FFlag::LuauTrackInteriorFreeTypesOnScope, true}
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local CollectionService = game:GetService("CollectionService")
|
|
local Players = game:GetService("Players")
|
|
|
|
local spawnCar = require(script.spawnCar)
|
|
local destroyPlayerCars = require(script.destroyPlayerCars)
|
|
|
|
local spawnPromptTemplate = script.SpawnPrompt
|
|
|
|
local KIOSK_TAG = "CarSpawnKiosk"
|
|
|
|
local function setupKiosk(kiosk: Model)
|
|
local spawnLocation = kiosk:FindFirstChild("SpawnLocation")
|
|
assert(spawnLocation, `{kiosk:GetFullName()} has no SpawnLocation part`)
|
|
local promptPart = kiosk:FindFirstChild("Prompt")
|
|
assert(promptPart, `{kiosk:GetFullName()} has no Prompt part`)
|
|
|
|
-- Hide the car spawn location
|
|
spawnLocation.Transparency = 1
|
|
|
|
-- Create a new prompt to spawn the car
|
|
local spawnPrompt = spawnPromptTemplate:Clone()
|
|
spawnPrompt.Parent = promptPart
|
|
|
|
spawnPrompt.Triggered:Connect(function(player: Player)
|
|
-- Remove any existing cars the player has spawned
|
|
destroyPlayerCars(player)
|
|
-- Spawn a new car at the spawnLocation, owned by the player
|
|
spawnCar(spawnLocation.CFrame, player)
|
|
end)
|
|
end
|
|
|
|
local function initialize()
|
|
-- Remove cars owned by players whenever they leave
|
|
Players.PlayerRemoving:Connect(destroyPlayerCars)
|
|
|
|
-- Setup all car spawning kiosks
|
|
CollectionService:GetInstanceAddedSignal(KIOSK_TAG):Connect(setupKiosk)
|
|
|
|
for _, kiosk in CollectionService:GetTagged(KIOSK_TAG) do
|
|
setupKiosk(kiosk)
|
|
end
|
|
end
|
|
|
|
initialize()
|
|
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_ERROR_COUNT(4, result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 7);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(
|
|
module->ats.typeInfo[0].node ==
|
|
"local function setupKiosk(kiosk: Model)\n local spawnLocation = kiosk:FindFirstChild('SpawnLocation')\n assert(spawnLocation, "
|
|
"`{kiosk:GetFullName()} has no SpawnLocation part`)\n local promptPart = kiosk:FindFirstChild('Prompt')\n assert(promptPart, "
|
|
"`{kiosk:GetFullName()} has no Prompt part`)\n\n\n spawnLocation.Transparency = 1\n\n\n local spawnPrompt = "
|
|
"spawnPromptTemplate:Clone()\n spawnPrompt.Parent = promptPart\n\n spawnPrompt.Triggered:Connect(function(player: Player)\n\n "
|
|
"destroyPlayerCars(player)\n\n spawnCar(spawnLocation.CFrame, player)\n end)\nend"
|
|
);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "mutually_recursive_generic")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
{FFlag::LuauDeferBidirectionalInferenceForTableAssignment, true}
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
--!strict
|
|
type T<a> = { f: a, g: U<a> }
|
|
type U<a> = { h: a, i: T<a>? }
|
|
local x: T<number> = { f = 37, g = { h = 5, i = nil } }
|
|
x.g.i = x
|
|
local y: T<string> = { f = "hi", g = { h = "lo", i = nil } }
|
|
y.g.i = y
|
|
)";
|
|
|
|
LUAU_REQUIRE_NO_ERRORS(frontend.check("game/Gui/Modules/A"));
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 0);
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "explicit_pack")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
type Foo<T...> = (T...) -> () -- also want to see how these are used.
|
|
type Bar = Foo<(number, any)>
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Bar = Foo<(number, any)>");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "local_val")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local a, b, c = 1 :: any
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Casts);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local a, b, c = 1 :: any");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "var_any_local")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local x = 2
|
|
local x: any = 2, 3
|
|
local x: any, y = 1, 2
|
|
local x: number, y: any, z, h: nil = 1, nil
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 2, 3");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "table_uses_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local x: any = 0
|
|
local y: number
|
|
local z = {x=x, y=y} -- not catching this
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: any = 0");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "typeof_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local x: any = 0
|
|
function some1(x: typeof(x))
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some1(x: typeof(x))\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "table_type_assigned")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local x: { x: any?} = {x = 1}
|
|
local z: { x : any, y : number? } -- not catching this
|
|
z.x = "bigfatlongstring"
|
|
z.y = nil
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Assign);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "local x: { x: any?} = {x = 1}");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "simple_func_wo_ret")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
function some(x: any)
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "function some(x: any)\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "simple_func_w_ret")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
function other(y: number): any
|
|
return "gotcha!"
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncRet);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "function other(y: number): any\n return 'gotcha!'\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "nested_local")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
function cool(y: number): number
|
|
local g: any = "gratatataaa"
|
|
return y
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::VarAnnot);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "function cool(y: number): number\n local g: any = 'gratatataaa'\n return y\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "generic_func")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
function reverse<T>(a: {T}, b: any): {T}
|
|
return a
|
|
end
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 1);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::FuncArg);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "function reverse<T>(a: {T}, b: any): {T}\n return a\n end");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "type_alias_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
type Clear = any
|
|
local z: Clear = "zip"
|
|
)";
|
|
|
|
CheckResult result1 = frontend.check("game/Gui/Modules/A");
|
|
LUAU_REQUIRE_NO_ERRORS(result1);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "multi_module_any")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/A"] = R"(
|
|
export type MyFunction = (number, string) -> (any)
|
|
)";
|
|
|
|
fileResolver.source["game/B"] = R"(
|
|
local MyFunc = require(script.Parent.A)
|
|
type Clear = any
|
|
local z: Clear = "zip"
|
|
)";
|
|
|
|
fileResolver.source["game/Gui/Modules/A"] = R"(
|
|
local Modules = game:GetService('Gui').Modules
|
|
local B = require(Modules.B)
|
|
return {hello = B.hello}
|
|
)";
|
|
|
|
CheckResult result = frontend.check("game/B");
|
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 2);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[0].node == "type Clear = any");
|
|
}
|
|
|
|
TEST_CASE_FIXTURE(ATSFixture, "cast_on_cyclic_req")
|
|
{
|
|
ScopedFastFlag sff[] = {
|
|
{FFlag::LuauSolverV2, true},
|
|
{FFlag::StudioReportLuauAny2, true},
|
|
};
|
|
|
|
fileResolver.source["game/A"] = R"(
|
|
local a = require(script.Parent.B) -- not resolving this module
|
|
export type MyFunction = (number, string) -> (any)
|
|
)";
|
|
|
|
fileResolver.source["game/B"] = R"(
|
|
local MyFunc = require(script.Parent.A) :: any
|
|
type Clear = any
|
|
local z: Clear = "zip"
|
|
)";
|
|
|
|
CheckResult result = frontend.check("game/B");
|
|
LUAU_REQUIRE_ERROR_COUNT(0, result);
|
|
|
|
ModulePtr module = frontend.moduleResolver.getModule("game/B");
|
|
|
|
LUAU_ASSERT(module->ats.typeInfo.size() == 3);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].code == Pattern::Alias);
|
|
LUAU_ASSERT(module->ats.typeInfo[1].node == "type Clear = any");
|
|
}
|
|
|
|
|
|
TEST_SUITE_END();
|