mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 14:25:44 +08:00
Pre-populate/duplicate check class definitions (new solver) (#1493)
Closes #1492
Tested and working with the test case in the aforementioned issue, along
with the full defs of luau-lsp with no issues or type errors
In normal Luau files, you can use type aliases and type functions before
they are declared. The same extends to declaration files, **except** in
the new solver. The old solver perfectly allows this, and in fact
intentionally adds it:
db809395bf/Analysis/src/TypeInfer.cpp (L1711-L1717)
This causes *much* headache and pain for external projects that make use
of declaration files; namely, luau-lsp generates them from MaximumADHD's
API dump, which is not ordered by dependency. This means silent
error-types popping up everywhere because types are used before they are
declared. The workaround would be to make code to manually reorder class
definitions based on their dependencies with a bunch of code, but this
is clearly not ideal, and won't work for classes dependent on each
other/recursive.
The solution used here is the same as is used for type aliases - the
name binding for the class is given a blocked type before running the
rest of constraint generation on the block. Questions remain:
- Should the logic be split off of `checkAliases`?
- Should a bound type be used, or should the (blocked) binding type be
directly emplaced with the class type? What are the ramifications of
emplacing with the bound versus the raw type? One ramification was
initially ran into through an assertion because the class
`superTy`/`parent` was bound, and several pieces of code assume it is
not, so it had to be made followed.
- Is folllowing `superTy` to set `parent` the correct workaround for the
assertions thrown, or should the code expecting `parent` to be a
ClassType without following it be modified instead to follow `parent`?
- Should `scope->privateTypeBindings` also be checked for the duplicate
error? I would presume so, since having a class with the same name as a
private alias or type function should error as well?
The extraneous whitespace changes are clang-format ones done
automatically that should've been done in the last release - I can
remove them if necessary and let another sync or OSS cleanup commit fix
it.
This commit is contained in:
parent
9a4bc6aeb8
commit
f1d4621d59
@ -34,6 +34,7 @@ LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
|||||||
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
LUAU_FASTFLAG(LuauTypestateBuiltins2)
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues)
|
LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
|
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -654,6 +655,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
|||||||
void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block)
|
void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* block)
|
||||||
{
|
{
|
||||||
std::unordered_map<Name, Location> aliasDefinitionLocations;
|
std::unordered_map<Name, Location> aliasDefinitionLocations;
|
||||||
|
std::unordered_map<Name, Location> classDefinitionLocations;
|
||||||
|
|
||||||
// In order to enable mutually-recursive type aliases, we need to
|
// In order to enable mutually-recursive type aliases, we need to
|
||||||
// populate the type bindings before we actually check any of the
|
// populate the type bindings before we actually check any of the
|
||||||
@ -751,6 +753,32 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
|
|||||||
scope->privateTypeBindings[function->name.value] = std::move(typeFunction);
|
scope->privateTypeBindings[function->name.value] = std::move(typeFunction);
|
||||||
aliasDefinitionLocations[function->name.value] = function->location;
|
aliasDefinitionLocations[function->name.value] = function->location;
|
||||||
}
|
}
|
||||||
|
else if (auto classDeclaration = stat->as<AstStatDeclareClass>())
|
||||||
|
{
|
||||||
|
if (!FFlag::LuauNewSolverPrePopulateClasses)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (scope->exportedTypeBindings.count(classDeclaration->name.value))
|
||||||
|
{
|
||||||
|
auto it = classDefinitionLocations.find(classDeclaration->name.value);
|
||||||
|
LUAU_ASSERT(it != classDefinitionLocations.end());
|
||||||
|
reportError(classDeclaration->location, DuplicateTypeDefinition{classDeclaration->name.value, it->second});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A class might have no name if the code is syntactically
|
||||||
|
// illegal. We mustn't prepopulate anything in this case.
|
||||||
|
if (classDeclaration->name == kParseNameError)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ScopePtr defnScope = childScope(classDeclaration, scope);
|
||||||
|
|
||||||
|
TypeId initialType = arena->addType(BlockedType{});
|
||||||
|
TypeFun initialFun{initialType};
|
||||||
|
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
|
||||||
|
|
||||||
|
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1646,6 +1674,11 @@ static bool isMetamethod(const Name& name)
|
|||||||
|
|
||||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
|
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass)
|
||||||
{
|
{
|
||||||
|
// If a class with the same name was already defined, we skip over
|
||||||
|
auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value);
|
||||||
|
if (FFlag::LuauNewSolverPrePopulateClasses && bindingIt == scope->exportedTypeBindings.end())
|
||||||
|
return ControlFlow::None;
|
||||||
|
|
||||||
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
|
std::optional<TypeId> superTy = std::make_optional(builtinTypes->classType);
|
||||||
if (declaredClass->superName)
|
if (declaredClass->superName)
|
||||||
{
|
{
|
||||||
@ -1660,7 +1693,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
|
|||||||
|
|
||||||
// We don't have generic classes, so this assertion _should_ never be hit.
|
// We don't have generic classes, so this assertion _should_ never be hit.
|
||||||
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
|
LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0);
|
||||||
superTy = lookupType->type;
|
if (FFlag::LuauNewSolverPrePopulateClasses)
|
||||||
|
superTy = follow(lookupType->type);
|
||||||
|
else
|
||||||
|
superTy = lookupType->type;
|
||||||
|
|
||||||
if (!get<ClassType>(follow(*superTy)))
|
if (!get<ClassType>(follow(*superTy)))
|
||||||
{
|
{
|
||||||
@ -1683,7 +1719,14 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas
|
|||||||
|
|
||||||
ctv->metatable = metaTy;
|
ctv->metatable = metaTy;
|
||||||
|
|
||||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
|
||||||
|
if (FFlag::LuauNewSolverPrePopulateClasses)
|
||||||
|
{
|
||||||
|
TypeId classBindTy = bindingIt->second.type;
|
||||||
|
emplaceType<BoundType>(asMutable(classBindTy), classTy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||||
|
|
||||||
if (declaredClass->indexer)
|
if (declaredClass->indexer)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauNewSolverPrePopulateClasses)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("DefinitionTests");
|
TEST_SUITE_BEGIN("DefinitionTests");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
|
TEST_CASE_FIXTURE(Fixture, "definition_file_simple")
|
||||||
@ -492,11 +494,8 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_indexer")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||||
{
|
{
|
||||||
unfreeze(frontend.globals.globalTypes);
|
ScopedFastFlag _{FFlag::LuauNewSolverPrePopulateClasses, true};
|
||||||
LoadDefinitionFileResult result = frontend.loadDefinitionFile(
|
loadDefinition(R"(
|
||||||
frontend.globals,
|
|
||||||
frontend.globals.globalScope,
|
|
||||||
R"(
|
|
||||||
declare class Channel
|
declare class Channel
|
||||||
Messages: { Message }
|
Messages: { Message }
|
||||||
OnMessage: (message: Message) -> ()
|
OnMessage: (message: Message) -> ()
|
||||||
@ -506,13 +505,19 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
|||||||
Text: string
|
Text: string
|
||||||
Channel: Channel
|
Channel: Channel
|
||||||
end
|
end
|
||||||
)",
|
)");
|
||||||
"@test",
|
|
||||||
/* captureComments */ false
|
|
||||||
);
|
|
||||||
freeze(frontend.globals.globalTypes);
|
|
||||||
|
|
||||||
REQUIRE(result.success);
|
CheckResult result = check(R"(
|
||||||
|
local a: Channel
|
||||||
|
local b = a.Messages[1]
|
||||||
|
local c = b.Channel
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
|
CHECK_EQ(toString(requireType("a")), "Channel");
|
||||||
|
CHECK_EQ(toString(requireType("b")), "Message");
|
||||||
|
CHECK_EQ(toString(requireType("c")), "Channel");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set")
|
||||||
|
Loading…
Reference in New Issue
Block a user