luau/Analysis/src/Quantify.cpp

309 lines
8.2 KiB
C++
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/Quantify.h"
2022-06-16 21:05:14 -04:00
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
#include "Luau/TxnLog.h"
#include "Luau/Substitution.h"
#include "Luau/VisitTypeVar.h"
#include "Luau/ConstraintGraphBuilder.h" // TODO for Scope2; move to separate header
LUAU_FASTFLAG(LuauAlwaysQuantify);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
2022-06-16 21:05:14 -04:00
LUAU_FASTFLAGVARIABLE(LuauQuantifyConstrained, false)
2022-04-14 19:57:43 -04:00
namespace Luau
{
2022-06-16 21:05:14 -04:00
/// @return true if outer encloses inner
static bool subsumes(Scope2* outer, Scope2* inner)
{
while (inner)
{
if (inner == outer)
return true;
inner = inner->parent;
}
return false;
}
2022-05-05 20:03:43 -04:00
struct Quantifier final : TypeVarOnceVisitor
{
TypeLevel level;
std::vector<TypeId> generics;
std::vector<TypePackId> genericPacks;
Scope2* scope = nullptr;
2022-04-14 19:57:43 -04:00
bool seenGenericType = false;
bool seenMutableType = false;
2022-05-05 20:03:43 -04:00
explicit Quantifier(TypeLevel level)
2022-02-17 20:18:01 -05:00
: level(level)
{
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
}
explicit Quantifier(Scope2* scope)
: scope(scope)
{
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
}
/// @return true if outer encloses inner
bool subsumes(Scope2* outer, Scope2* inner)
{
while (inner)
{
if (inner == outer)
return true;
inner = inner->parent;
}
return false;
}
2022-05-05 20:03:43 -04:00
bool visit(TypeId ty, const FreeTypeVar& ftv) override
{
2022-05-19 20:02:24 -04:00
seenMutableType = true;
2022-05-05 20:03:43 -04:00
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftv.scope) : !level.subsumes(ftv.level))
2022-05-05 20:03:43 -04:00
return false;
if (FFlag::DebugLuauDeferredConstraintResolution)
*asMutable(ty) = GenericTypeVar{scope};
else
*asMutable(ty) = GenericTypeVar{level};
2022-05-05 20:03:43 -04:00
generics.push_back(ty);
return false;
}
2022-06-16 21:05:14 -04:00
bool visit(TypeId ty, const ConstrainedTypeVar&) override
{
if (FFlag::LuauQuantifyConstrained)
{
ConstrainedTypeVar* ctv = getMutable<ConstrainedTypeVar>(ty);
seenMutableType = true;
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ctv->scope) : !level.subsumes(ctv->level))
return false;
std::vector<TypeId> opts = std::move(ctv->parts);
// We might transmute, so it's not safe to rely on the builtin traversal logic
for (TypeId opt : opts)
traverse(opt);
if (opts.size() == 1)
*asMutable(ty) = BoundTypeVar{opts[0]};
else
*asMutable(ty) = UnionTypeVar{std::move(opts)};
return false;
}
else
return true;
}
2022-05-05 20:03:43 -04:00
bool visit(TypeId ty, const TableTypeVar&) override
{
LUAU_ASSERT(getMutable<TableTypeVar>(ty));
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
2022-05-19 20:02:24 -04:00
if (ttv.state == TableState::Generic)
seenGenericType = true;
2022-04-14 19:57:43 -04:00
2022-05-19 20:02:24 -04:00
if (ttv.state == TableState::Free)
seenMutableType = true;
2022-04-14 19:57:43 -04:00
2022-06-16 21:05:14 -04:00
if (!FFlag::LuauQuantifyConstrained)
{
if (ttv.state == TableState::Sealed || ttv.state == TableState::Generic)
return false;
}
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ttv.scope) : !level.subsumes(ttv.level))
2022-04-14 19:57:43 -04:00
{
2022-05-19 20:02:24 -04:00
if (ttv.state == TableState::Unsealed)
2022-04-14 19:57:43 -04:00
seenMutableType = true;
return false;
2022-04-14 19:57:43 -04:00
}
if (ttv.state == TableState::Free)
2022-04-14 19:57:43 -04:00
{
ttv.state = TableState::Generic;
2022-05-19 20:02:24 -04:00
seenGenericType = true;
2022-04-14 19:57:43 -04:00
}
else if (ttv.state == TableState::Unsealed)
ttv.state = TableState::Sealed;
ttv.level = level;
return true;
}
2022-05-05 20:03:43 -04:00
bool visit(TypePackId tp, const FreeTypePack& ftp) override
{
2022-05-19 20:02:24 -04:00
seenMutableType = true;
2022-04-14 19:57:43 -04:00
if (FFlag::DebugLuauDeferredConstraintResolution ? !subsumes(scope, ftp.scope) : !level.subsumes(ftp.level))
return false;
*asMutable(tp) = GenericTypePack{level};
genericPacks.push_back(tp);
return true;
}
};
2022-02-17 20:18:01 -05:00
void quantify(TypeId ty, TypeLevel level)
{
2022-02-17 20:18:01 -05:00
Quantifier q{level};
q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
2022-05-19 20:02:24 -04:00
if (FFlag::LuauAlwaysQuantify)
{
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
2022-04-14 19:57:43 -04:00
2022-05-19 20:02:24 -04:00
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
2022-04-14 19:57:43 -04:00
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
void quantify(TypeId ty, Scope2* scope)
{
Quantifier q{scope};
q.traverse(ty);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(ty);
LUAU_ASSERT(ftv);
if (FFlag::LuauAlwaysQuantify)
{
ftv->generics.insert(ftv->generics.end(), q.generics.begin(), q.generics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), q.genericPacks.begin(), q.genericPacks.end());
}
else
{
ftv->generics = q.generics;
ftv->genericPacks = q.genericPacks;
}
if (ftv->generics.empty() && ftv->genericPacks.empty() && !q.seenMutableType && !q.seenGenericType)
ftv->hasNoGenerics = true;
ftv->generalized = true;
}
2022-06-16 21:05:14 -04:00
struct PureQuantifier : Substitution
{
Scope2* scope;
std::vector<TypeId> insertedGenerics;
std::vector<TypePackId> insertedGenericPacks;
PureQuantifier(const TxnLog* log, TypeArena* arena, Scope2* scope)
: Substitution(log, arena)
, scope(scope)
{
}
bool isDirty(TypeId ty) override
{
LUAU_ASSERT(ty == follow(ty));
if (auto ftv = get<FreeTypeVar>(ty))
{
return subsumes(scope, ftv->scope);
}
else if (auto ttv = get<TableTypeVar>(ty))
{
return ttv->state == TableState::Free && subsumes(scope, ttv->scope);
}
return false;
}
bool isDirty(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
return subsumes(scope, ftp->scope);
}
return false;
}
TypeId clean(TypeId ty) override
{
if (auto ftv = get<FreeTypeVar>(ty))
{
TypeId result = arena->addType(GenericTypeVar{});
insertedGenerics.push_back(result);
return result;
}
else if (auto ttv = get<TableTypeVar>(ty))
{
TypeId result = arena->addType(TableTypeVar{});
TableTypeVar* resultTable = getMutable<TableTypeVar>(result);
LUAU_ASSERT(resultTable);
*resultTable = *ttv;
resultTable->scope = nullptr;
resultTable->state = TableState::Generic;
return result;
}
return ty;
}
TypePackId clean(TypePackId tp) override
{
if (auto ftp = get<FreeTypePack>(tp))
{
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{}});
insertedGenericPacks.push_back(result);
return result;
}
return tp;
}
bool ignoreChildren(TypeId ty) override
{
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
TypeId quantify(TypeArena* arena, TypeId ty, Scope2* scope)
{
PureQuantifier quantifier{TxnLog::empty(), arena, scope};
std::optional<TypeId> result = quantifier.substitute(ty);
LUAU_ASSERT(result);
FunctionTypeVar* ftv = getMutable<FunctionTypeVar>(*result);
LUAU_ASSERT(ftv);
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
// TODO: Set hasNoGenerics.
return *result;
}
} // namespace Luau