2022-06-03 23:15:45 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
|
|
|
|
#include "Luau/ConstraintSolver.h"
|
|
|
|
#include "Luau/Instantiation.h"
|
2022-06-24 02:56:00 +01:00
|
|
|
#include "Luau/Location.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/Quantify.h"
|
|
|
|
#include "Luau/ToString.h"
|
|
|
|
#include "Luau/Unifier.h"
|
|
|
|
|
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
2022-06-17 02:05:14 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
[[maybe_unused]] static void dumpBindings(NotNull<Scope2> scope, ToStringOptions& opts)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
for (const auto& [k, v] : scope->bindings)
|
|
|
|
{
|
|
|
|
auto d = toStringDetailed(v, opts);
|
|
|
|
opts.nameMap = d.nameMap;
|
|
|
|
printf("\t%s : %s\n", k.c_str(), d.name.c_str());
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
for (NotNull<Scope2> child : scope->children)
|
2022-06-03 23:15:45 +01:00
|
|
|
dumpBindings(child, opts);
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
static void dumpConstraints(NotNull<Scope2> scope, ToStringOptions& opts)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
for (const ConstraintPtr& c : scope->constraints)
|
|
|
|
{
|
|
|
|
printf("\t%s\n", toString(*c, opts).c_str());
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
for (NotNull<Scope2> child : scope->children)
|
2022-06-03 23:15:45 +01:00
|
|
|
dumpConstraints(child, opts);
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
void dump(NotNull<Scope2> rootScope, ToStringOptions& opts)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
printf("constraints:\n");
|
|
|
|
dumpConstraints(rootScope, opts);
|
|
|
|
}
|
|
|
|
|
|
|
|
void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
|
|
|
{
|
|
|
|
printf("constraints:\n");
|
|
|
|
for (const Constraint* c : cs->unsolvedConstraints)
|
|
|
|
{
|
|
|
|
printf("\t%s\n", toString(*c, opts).c_str());
|
|
|
|
|
|
|
|
for (const Constraint* dep : c->dependencies)
|
|
|
|
printf("\t\t%s\n", toString(*dep, opts).c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
ConstraintSolver::ConstraintSolver(TypeArena* arena, NotNull<Scope2> rootScope)
|
2022-06-03 23:15:45 +01:00
|
|
|
: arena(arena)
|
|
|
|
, constraints(collectConstraints(rootScope))
|
|
|
|
, rootScope(rootScope)
|
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
for (NotNull<Constraint> c : constraints)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
unsolvedConstraints.push_back(c);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
for (NotNull<const Constraint> dep : c->dependencies)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
block(dep, c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::run()
|
|
|
|
{
|
|
|
|
if (done())
|
|
|
|
return;
|
|
|
|
|
|
|
|
ToStringOptions opts;
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolver)
|
|
|
|
{
|
|
|
|
printf("Starting solver\n");
|
|
|
|
dump(this, opts);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
auto runSolverPass = [&](bool force) {
|
|
|
|
bool progress = false;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
size_t i = 0;
|
|
|
|
while (i < unsolvedConstraints.size())
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
NotNull<const Constraint> c = unsolvedConstraints[i];
|
|
|
|
if (!force && isBlocked(c))
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
++i;
|
2022-06-03 23:15:45 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{};
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
|
|
|
logger.prepareStepSnapshot(rootScope, c, unsolvedConstraints);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool success = tryDispatch(c, force);
|
|
|
|
|
|
|
|
progress |= success;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
if (success)
|
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
unsolvedConstraints.erase(unsolvedConstraints.begin() + i);
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
|
|
|
logger.commitPreparedStepSnapshot();
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
if (FFlag::DebugLuauLogSolver)
|
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (force)
|
|
|
|
printf("Force ");
|
2022-06-03 23:15:45 +01:00
|
|
|
printf("Dispatched\n\t%s\n", saveMe.c_str());
|
|
|
|
dump(this, opts);
|
|
|
|
}
|
|
|
|
}
|
2022-06-17 02:05:14 +01:00
|
|
|
else
|
|
|
|
++i;
|
|
|
|
|
|
|
|
if (force && success)
|
|
|
|
return true;
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
return progress;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool progress = false;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
progress = runSolverPass(false);
|
|
|
|
if (!progress)
|
|
|
|
progress |= runSolverPass(true);
|
2022-06-03 23:15:45 +01:00
|
|
|
} while (progress);
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolver)
|
2022-06-17 02:05:14 +01:00
|
|
|
{
|
2022-06-03 23:15:45 +01:00
|
|
|
dumpBindings(rootScope, opts);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
|
|
|
logger.captureBoundarySnapshot(rootScope, unsolvedConstraints);
|
|
|
|
printf("Logger output:\n%s\n", logger.compileOutput().c_str());
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::done()
|
|
|
|
{
|
|
|
|
return unsolvedConstraints.empty();
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (!force && isBlocked(constraint))
|
2022-06-03 23:15:45 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
|
|
|
|
if (auto sc = get<SubtypeConstraint>(*constraint))
|
2022-06-17 02:05:14 +01:00
|
|
|
success = tryDispatch(*sc, constraint, force);
|
2022-06-03 23:15:45 +01:00
|
|
|
else if (auto psc = get<PackSubtypeConstraint>(*constraint))
|
2022-06-17 02:05:14 +01:00
|
|
|
success = tryDispatch(*psc, constraint, force);
|
2022-06-03 23:15:45 +01:00
|
|
|
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
2022-06-17 02:05:14 +01:00
|
|
|
success = tryDispatch(*gc, constraint, force);
|
2022-06-03 23:15:45 +01:00
|
|
|
else if (auto ic = get<InstantiationConstraint>(*constraint))
|
2022-06-17 02:05:14 +01:00
|
|
|
success = tryDispatch(*ic, constraint, force);
|
2022-07-01 00:52:43 +01:00
|
|
|
else if (auto uc = get<UnaryConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*uc, constraint, force);
|
|
|
|
else if (auto bc = get<BinaryConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*bc, constraint, force);
|
2022-06-24 02:56:00 +01:00
|
|
|
else if (auto nc = get<NameConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*nc, constraint);
|
2022-06-03 23:15:45 +01:00
|
|
|
else
|
|
|
|
LUAU_ASSERT(0);
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
unblock(constraint);
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (isBlocked(c.subType))
|
|
|
|
return block(c.subType, constraint);
|
|
|
|
else if (isBlocked(c.superType))
|
|
|
|
return block(c.superType, constraint);
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
unify(c.subType, c.superType);
|
2022-06-17 02:05:14 +01:00
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
unblock(c.subType);
|
|
|
|
unblock(c.superType);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-24 02:56:00 +01:00
|
|
|
unify(c.subPack, c.superPack);
|
2022-06-03 23:15:45 +01:00
|
|
|
unblock(c.subPack);
|
|
|
|
unblock(c.superPack);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (isBlocked(c.sourceType))
|
|
|
|
return block(c.sourceType, constraint);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (isBlocked(c.generalizedType))
|
|
|
|
asMutable(c.generalizedType)->ty.emplace<BoundTypeVar>(c.sourceType);
|
|
|
|
else
|
2022-06-24 02:56:00 +01:00
|
|
|
unify(c.generalizedType, c.sourceType);
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
TypeId generalized = quantify(arena, c.sourceType, c.scope);
|
|
|
|
*asMutable(c.sourceType) = *generalized;
|
|
|
|
|
|
|
|
unblock(c.generalizedType);
|
|
|
|
unblock(c.sourceType);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
if (isBlocked(c.superType))
|
|
|
|
return block(c.superType, constraint);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
Instantiation inst(TxnLog::empty(), arena, TypeLevel{});
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
|
|
|
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
if (isBlocked(c.subType))
|
|
|
|
asMutable(c.subType)->ty.emplace<BoundTypeVar>(*instantiated);
|
|
|
|
else
|
|
|
|
unify(c.subType, *instantiated);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
unblock(c.subType);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-07-01 00:52:43 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
TypeId operandType = follow(c.operandType);
|
|
|
|
|
|
|
|
if (isBlocked(operandType))
|
|
|
|
return block(operandType, constraint);
|
|
|
|
|
|
|
|
if (get<FreeTypeVar>(operandType))
|
|
|
|
return block(operandType, constraint);
|
|
|
|
|
|
|
|
LUAU_ASSERT(get<BlockedTypeVar>(c.resultType));
|
|
|
|
|
|
|
|
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
|
|
|
|
{
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
LUAU_ASSERT(0); // TODO metatable handling
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
TypeId leftType = follow(c.leftType);
|
|
|
|
TypeId rightType = follow(c.rightType);
|
|
|
|
|
|
|
|
if (isBlocked(leftType) || isBlocked(rightType))
|
|
|
|
{
|
|
|
|
block(leftType, constraint);
|
|
|
|
block(rightType, constraint);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isNumber(leftType))
|
|
|
|
{
|
|
|
|
unify(leftType, rightType);
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(leftType);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<FreeTypeVar>(leftType) && !force)
|
|
|
|
return block(leftType, constraint);
|
|
|
|
|
|
|
|
// TODO metatables, classes
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
if (isBlocked(c.namedType))
|
|
|
|
return block(c.namedType, constraint);
|
|
|
|
|
|
|
|
TypeId target = follow(c.namedType);
|
|
|
|
if (TableTypeVar* ttv = getMutable<TableTypeVar>(target))
|
|
|
|
ttv->name = c.name;
|
|
|
|
else if (MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(target))
|
|
|
|
mtv->syntheticName = c.name;
|
|
|
|
else
|
|
|
|
return block(c.namedType, constraint);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
blocked[target].push_back(constraint);
|
|
|
|
|
|
|
|
auto& count = blockedConstraints[constraint];
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
void ConstraintSolver::block(NotNull<const Constraint> target, NotNull<const Constraint> constraint)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
block_(target, constraint);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::block(TypeId target, NotNull<const Constraint> constraint)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
block_(target, constraint);
|
2022-06-17 02:05:14 +01:00
|
|
|
return false;
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constraint)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
block_(target, constraint);
|
2022-06-17 02:05:14 +01:00
|
|
|
return false;
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
|
|
|
|
{
|
|
|
|
auto it = blocked.find(progressed);
|
|
|
|
if (it == blocked.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// unblocked should contain a value always, because of the above check
|
2022-06-17 02:05:14 +01:00
|
|
|
for (NotNull<const Constraint> unblockedConstraint : it->second)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
auto& count = blockedConstraints[unblockedConstraint];
|
|
|
|
// This assertion being hit indicates that `blocked` and
|
|
|
|
// `blockedConstraints` desynchronized at some point. This is problematic
|
|
|
|
// because we rely on this count being correct to skip over blocked
|
|
|
|
// constraints.
|
|
|
|
LUAU_ASSERT(count > 0);
|
|
|
|
count -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
blocked.erase(it);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock(TypeId progressed)
|
|
|
|
{
|
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock(TypePackId progressed)
|
|
|
|
{
|
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::isBlocked(TypeId ty)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
return nullptr != get<BlockedTypeVar>(follow(ty));
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-06-17 02:05:14 +01:00
|
|
|
auto blockedIt = blockedConstraints.find(constraint);
|
|
|
|
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
void ConstraintSolver::unify(TypeId subType, TypeId superType)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
UnifierSharedState sharedState{&iceReporter};
|
2022-06-24 02:56:00 +01:00
|
|
|
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
u.tryUnify(subType, superType);
|
|
|
|
u.log.commit();
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
UnifierSharedState sharedState{&iceReporter};
|
2022-06-24 02:56:00 +01:00
|
|
|
Unifier u{arena, Mode::Strict, Location{}, Covariant, sharedState};
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
u.tryUnify(subPack, superPack);
|
|
|
|
u.log.commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|