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
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
#include "Luau/Anyification.h"
|
2022-08-04 23:35:33 +01:00
|
|
|
#include "Luau/ApplyTypeFunction.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/ConstraintSolver.h"
|
|
|
|
#include "Luau/Instantiation.h"
|
2022-06-24 02:56:00 +01:00
|
|
|
#include "Luau/Location.h"
|
2022-09-02 00:14:03 +01:00
|
|
|
#include "Luau/ModuleResolver.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/Quantify.h"
|
|
|
|
#include "Luau/ToString.h"
|
2022-09-29 23:23:10 +01:00
|
|
|
#include "Luau/TypeVar.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
#include "Luau/Unifier.h"
|
2022-09-08 23:14:25 +01:00
|
|
|
#include "Luau/DcrLogger.h"
|
2022-08-04 23:35:33 +01:00
|
|
|
#include "Luau/VisitTypeVar.h"
|
2022-09-29 23:23:10 +01:00
|
|
|
#include "Luau/TypeUtils.h"
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
2022-06-17 02:05:14 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false);
|
2022-08-25 22:53:50 +01:00
|
|
|
LUAU_FASTFLAG(LuauFixNameMaps)
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2022-07-29 05:24:07 +01:00
|
|
|
[[maybe_unused]] static void dumpBindings(NotNull<Scope> scope, ToStringOptions& opts)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
for (const auto& [k, v] : scope->bindings)
|
|
|
|
{
|
2022-08-25 22:53:50 +01:00
|
|
|
if (FFlag::LuauFixNameMaps)
|
|
|
|
{
|
|
|
|
auto d = toString(v.typeId, opts);
|
|
|
|
printf("\t%s : %s\n", k.c_str(), d.c_str());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto d = toStringDetailed(v.typeId, opts);
|
|
|
|
opts.DEPRECATED_nameMap = d.DEPRECATED_nameMap;
|
|
|
|
printf("\t%s : %s\n", k.c_str(), d.name.c_str());
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-07-29 05:24:07 +01:00
|
|
|
for (NotNull<Scope> child : scope->children)
|
2022-06-03 23:15:45 +01:00
|
|
|
dumpBindings(child, opts);
|
|
|
|
}
|
|
|
|
|
2022-07-29 05:24:07 +01:00
|
|
|
static void dumpConstraints(NotNull<Scope> 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-29 05:24:07 +01:00
|
|
|
for (NotNull<Scope> child : scope->children)
|
2022-06-03 23:15:45 +01:00
|
|
|
dumpConstraints(child, opts);
|
|
|
|
}
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(TypeArena* arena, NotNull<SingletonTypes> singletonTypes,
|
|
|
|
const TypeFun& fn, const std::vector<TypeId>& rawTypeArguments, const std::vector<TypePackId>& rawPackArguments)
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
|
|
|
std::vector<TypeId> saturatedTypeArguments;
|
|
|
|
std::vector<TypeId> extraTypes;
|
|
|
|
std::vector<TypePackId> saturatedPackArguments;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < rawTypeArguments.size(); ++i)
|
|
|
|
{
|
|
|
|
TypeId ty = rawTypeArguments[i];
|
|
|
|
|
|
|
|
if (i < fn.typeParams.size())
|
|
|
|
saturatedTypeArguments.push_back(ty);
|
|
|
|
else
|
|
|
|
extraTypes.push_back(ty);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we collected extra types, put them in a type pack now. This case is
|
|
|
|
// mutually exclusive with the type pack -> type conversion we do below:
|
|
|
|
// extraTypes will only have elements in it if we have more types than we
|
|
|
|
// have parameter slots for them to go into.
|
|
|
|
if (!extraTypes.empty())
|
|
|
|
{
|
|
|
|
saturatedPackArguments.push_back(arena->addTypePack(extraTypes));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < rawPackArguments.size(); ++i)
|
|
|
|
{
|
|
|
|
TypePackId tp = rawPackArguments[i];
|
|
|
|
|
|
|
|
// If we are short on regular type saturatedTypeArguments and we have a single
|
|
|
|
// element type pack, we can decompose that to the type it contains and
|
|
|
|
// use that as a type parameter.
|
|
|
|
if (saturatedTypeArguments.size() < fn.typeParams.size() && size(tp) == 1 && finite(tp) && first(tp) && saturatedPackArguments.empty())
|
|
|
|
{
|
|
|
|
saturatedTypeArguments.push_back(*first(tp));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
saturatedPackArguments.push_back(tp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t typesProvided = saturatedTypeArguments.size();
|
|
|
|
size_t typesRequired = fn.typeParams.size();
|
|
|
|
|
|
|
|
size_t packsProvided = saturatedPackArguments.size();
|
|
|
|
size_t packsRequired = fn.typePackParams.size();
|
|
|
|
|
|
|
|
// Extra types should be accumulated in extraTypes, not saturatedTypeArguments. Extra
|
|
|
|
// packs will be accumulated in saturatedPackArguments, so we don't have an
|
|
|
|
// assertion for that.
|
|
|
|
LUAU_ASSERT(typesProvided <= typesRequired);
|
|
|
|
|
|
|
|
// If we didn't provide enough types, but we did provide a type pack, we
|
|
|
|
// don't want to use defaults. The rationale for this is that if the user
|
|
|
|
// provides a pack but doesn't provide enough types, we want to report an
|
|
|
|
// error, rather than simply using the default saturatedTypeArguments, if they exist. If
|
|
|
|
// they did provide enough types, but not enough packs, we of course want to
|
|
|
|
// use the default packs.
|
|
|
|
bool needsDefaults = (typesProvided < typesRequired && packsProvided == 0) || (typesProvided == typesRequired && packsProvided < packsRequired);
|
|
|
|
|
|
|
|
if (needsDefaults)
|
|
|
|
{
|
|
|
|
// Default types can reference earlier types. It's legal to write
|
|
|
|
// something like
|
|
|
|
// type T<A, B = A> = (A, B) -> number
|
|
|
|
// and we need to respect that. We use an ApplyTypeFunction for this.
|
|
|
|
ApplyTypeFunction atf{arena};
|
|
|
|
|
|
|
|
for (size_t i = 0; i < typesProvided; ++i)
|
|
|
|
atf.typeArguments[fn.typeParams[i].ty] = saturatedTypeArguments[i];
|
|
|
|
|
|
|
|
for (size_t i = typesProvided; i < typesRequired; ++i)
|
|
|
|
{
|
|
|
|
TypeId defaultTy = fn.typeParams[i].defaultValue.value_or(nullptr);
|
|
|
|
|
|
|
|
// We will fill this in with the error type later.
|
|
|
|
if (!defaultTy)
|
|
|
|
break;
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
TypeId instantiatedDefault = atf.substitute(defaultTy).value_or(singletonTypes->errorRecoveryType());
|
2022-08-04 23:35:33 +01:00
|
|
|
atf.typeArguments[fn.typeParams[i].ty] = instantiatedDefault;
|
|
|
|
saturatedTypeArguments.push_back(instantiatedDefault);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < packsProvided; ++i)
|
|
|
|
{
|
|
|
|
atf.typePackArguments[fn.typePackParams[i].tp] = saturatedPackArguments[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = packsProvided; i < packsRequired; ++i)
|
|
|
|
{
|
|
|
|
TypePackId defaultTp = fn.typePackParams[i].defaultValue.value_or(nullptr);
|
|
|
|
|
|
|
|
// We will fill this in with the error type pack later.
|
|
|
|
if (!defaultTp)
|
|
|
|
break;
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
TypePackId instantiatedDefault = atf.substitute(defaultTp).value_or(singletonTypes->errorRecoveryTypePack());
|
2022-08-04 23:35:33 +01:00
|
|
|
atf.typePackArguments[fn.typePackParams[i].tp] = instantiatedDefault;
|
|
|
|
saturatedPackArguments.push_back(instantiatedDefault);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we didn't create an extra type pack from overflowing parameter packs,
|
|
|
|
// and we're still missing a type pack, plug in an empty type pack as the
|
|
|
|
// value of the empty packs.
|
|
|
|
if (extraTypes.empty() && saturatedPackArguments.size() + 1 == fn.typePackParams.size())
|
|
|
|
{
|
|
|
|
saturatedPackArguments.push_back(arena->addTypePack({}));
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need to have _something_ when we substitute the generic saturatedTypeArguments,
|
|
|
|
// even if they're missing, so we use the error type as a filler.
|
|
|
|
for (size_t i = saturatedTypeArguments.size(); i < typesRequired; ++i)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
saturatedTypeArguments.push_back(singletonTypes->errorRecoveryType());
|
2022-08-04 23:35:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = saturatedPackArguments.size(); i < packsRequired; ++i)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
saturatedPackArguments.push_back(singletonTypes->errorRecoveryTypePack());
|
2022-08-04 23:35:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, these two conditions should be true. If they aren't we
|
|
|
|
// will run into access violations.
|
|
|
|
LUAU_ASSERT(saturatedTypeArguments.size() == fn.typeParams.size());
|
|
|
|
LUAU_ASSERT(saturatedPackArguments.size() == fn.typePackParams.size());
|
|
|
|
|
|
|
|
return {saturatedTypeArguments, saturatedPackArguments};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool InstantiationSignature::operator==(const InstantiationSignature& rhs) const
|
|
|
|
{
|
|
|
|
return fn == rhs.fn && arguments == rhs.arguments && packArguments == rhs.packArguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t HashInstantiationSignature::operator()(const InstantiationSignature& signature) const
|
|
|
|
{
|
|
|
|
size_t hash = std::hash<TypeId>{}(signature.fn.type);
|
|
|
|
for (const GenericTypeDefinition& p : signature.fn.typeParams)
|
|
|
|
{
|
|
|
|
hash ^= (std::hash<TypeId>{}(p.ty) << 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const GenericTypePackDefinition& p : signature.fn.typePackParams)
|
|
|
|
{
|
|
|
|
hash ^= (std::hash<TypePackId>{}(p.tp) << 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const TypeId a : signature.arguments)
|
|
|
|
{
|
|
|
|
hash ^= (std::hash<TypeId>{}(a) << 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const TypePackId a : signature.packArguments)
|
|
|
|
{
|
|
|
|
hash ^= (std::hash<TypePackId>{}(a) << 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2022-07-29 05:24:07 +01:00
|
|
|
void dump(NotNull<Scope> 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");
|
2022-08-25 22:53:50 +01:00
|
|
|
for (NotNull<const Constraint> c : cs->unsolvedConstraints)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-08-25 22:53:50 +01:00
|
|
|
auto it = cs->blockedConstraints.find(c);
|
|
|
|
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
|
|
|
|
printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str());
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
for (NotNull<Constraint> dep : c->dependencies)
|
|
|
|
{
|
|
|
|
auto unsolvedIter = std::find(begin(cs->unsolvedConstraints), end(cs->unsolvedConstraints), dep);
|
|
|
|
if (unsolvedIter == cs->unsolvedConstraints.end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
auto it = cs->blockedConstraints.find(dep);
|
|
|
|
int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second);
|
|
|
|
printf("\t%d\t\t%s\n", blockCount, toString(*dep, opts).c_str());
|
|
|
|
}
|
2022-09-15 23:38:17 +01:00
|
|
|
|
|
|
|
if (auto fcc = get<FunctionCallConstraint>(*c))
|
|
|
|
{
|
|
|
|
for (NotNull<const Constraint> inner : fcc->innerConstraints)
|
|
|
|
printf("\t\t\t%s\n", toString(*inner, opts).c_str());
|
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-07 01:23:29 +01:00
|
|
|
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, ModuleName moduleName,
|
2022-09-08 23:14:25 +01:00
|
|
|
NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
2022-10-07 01:23:29 +01:00
|
|
|
: arena(normalizer->arena)
|
|
|
|
, singletonTypes(normalizer->singletonTypes)
|
|
|
|
, normalizer(normalizer)
|
2022-06-03 23:15:45 +01:00
|
|
|
, constraints(collectConstraints(rootScope))
|
|
|
|
, rootScope(rootScope)
|
2022-09-02 00:14:03 +01:00
|
|
|
, currentModuleName(std::move(moduleName))
|
|
|
|
, moduleResolver(moduleResolver)
|
|
|
|
, requireCycles(requireCycles)
|
2022-09-08 23:14:25 +01:00
|
|
|
, logger(logger)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
opts.exhaustive = true;
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 23:14:25 +01:00
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
LUAU_ASSERT(logger);
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-07 01:23:29 +01:00
|
|
|
void ConstraintSolver::randomize(unsigned seed)
|
|
|
|
{
|
2022-10-14 20:48:41 +01:00
|
|
|
if (unsolvedConstraints.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
unsigned int rng = seed;
|
|
|
|
|
|
|
|
for (size_t i = unsolvedConstraints.size() - 1; i > 0; --i)
|
|
|
|
{
|
|
|
|
// Fisher-Yates shuffle
|
|
|
|
size_t j = rng % (i + 1);
|
|
|
|
|
|
|
|
std::swap(unsolvedConstraints[i], unsolvedConstraints[j]);
|
|
|
|
|
|
|
|
// LCG RNG, constants from Numerical Recipes
|
|
|
|
// This may occasionally result in skewed shuffles due to distribution properties, but this is a debugging tool so it should be good enough
|
|
|
|
rng = rng * 1664525 + 1013904223;
|
|
|
|
}
|
2022-10-07 01:23:29 +01:00
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
void ConstraintSolver::run()
|
|
|
|
{
|
2022-10-14 20:48:41 +01:00
|
|
|
if (isDone())
|
2022-06-03 23:15:45 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
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-09-08 23:14:25 +01:00
|
|
|
logger->captureInitialSolverState(rootScope, unsolvedConstraints);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
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-09-08 23:14:25 +01:00
|
|
|
StepSnapshot snapshot;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool success = tryDispatch(c, force);
|
|
|
|
|
|
|
|
progress |= success;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
if (success)
|
|
|
|
{
|
2022-08-25 22:53:50 +01:00
|
|
|
unblock(c);
|
2022-06-17 02:05:14 +01:00
|
|
|
unsolvedConstraints.erase(unsolvedConstraints.begin() + i);
|
|
|
|
|
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
logger->commitStepSnapshot(snapshot);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
2022-10-14 20:48:41 +01:00
|
|
|
finalizeModule();
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
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)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
logger->captureFinalSolverState(rootScope, unsolvedConstraints);
|
2022-06-17 02:05:14 +01:00
|
|
|
}
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-14 20:48:41 +01:00
|
|
|
bool ConstraintSolver::isDone()
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
return unsolvedConstraints.empty();
|
|
|
|
}
|
|
|
|
|
2022-10-14 20:48:41 +01:00
|
|
|
void ConstraintSolver::finalizeModule()
|
|
|
|
{
|
|
|
|
Anyification a{arena, rootScope, singletonTypes, &iceReporter, singletonTypes->anyType, singletonTypes->anyTypePack};
|
|
|
|
std::optional<TypePackId> returnType = a.substitute(rootScope->returnType);
|
|
|
|
if (!returnType)
|
|
|
|
{
|
|
|
|
reportError(CodeTooComplex{}, Location{});
|
|
|
|
rootScope->returnType = singletonTypes->errorTypePack;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rootScope->returnType = *returnType;
|
|
|
|
}
|
|
|
|
|
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-09-02 00:14:03 +01:00
|
|
|
else if (auto ic = get<IterableConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*ic, constraint, force);
|
2022-06-24 02:56:00 +01:00
|
|
|
else if (auto nc = get<NameConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*nc, constraint);
|
2022-08-04 23:35:33 +01:00
|
|
|
else if (auto taec = get<TypeAliasExpansionConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*taec, constraint);
|
2022-09-02 00:14:03 +01:00
|
|
|
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*fcc, constraint);
|
2022-09-23 20:17:25 +01:00
|
|
|
else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*fcc, constraint);
|
|
|
|
else if (auto hpc = get<HasPropConstraint>(*constraint))
|
|
|
|
success = tryDispatch(*hpc, constraint);
|
2022-06-03 23:15:45 +01:00
|
|
|
else
|
2022-09-23 20:17:25 +01:00
|
|
|
LUAU_ASSERT(false);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
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-09-23 20:17:25 +01:00
|
|
|
if (!recursiveBlock(c.subType, constraint))
|
|
|
|
return false;
|
|
|
|
if (!recursiveBlock(c.superType, constraint))
|
|
|
|
return false;
|
|
|
|
|
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-08-18 22:32:08 +01:00
|
|
|
unify(c.subType, c.superType, constraint->scope);
|
2022-06-17 02:05:14 +01:00
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
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-09-23 20:17:25 +01:00
|
|
|
if (!recursiveBlock(c.subPack, constraint) || !recursiveBlock(c.superPack, constraint))
|
|
|
|
return false;
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
if (isBlocked(c.subPack))
|
|
|
|
return block(c.subPack, constraint);
|
|
|
|
else if (isBlocked(c.superPack))
|
|
|
|
return block(c.superPack, constraint);
|
|
|
|
|
2022-08-18 22:32:08 +01:00
|
|
|
unify(c.subPack, c.superPack, constraint->scope);
|
2022-09-29 23:23:10 +01:00
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
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-08-25 22:53:50 +01:00
|
|
|
TypeId generalized = quantify(arena, c.sourceType, constraint->scope);
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
if (isBlocked(c.generalizedType))
|
2022-08-25 22:53:50 +01:00
|
|
|
asMutable(c.generalizedType)->ty.emplace<BoundTypeVar>(generalized);
|
2022-06-17 02:05:14 +01:00
|
|
|
else
|
2022-08-25 22:53:50 +01:00
|
|
|
unify(c.generalizedType, generalized, constraint->scope);
|
2022-06-17 02:05:14 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
2022-09-29 23:23:10 +01:00
|
|
|
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
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
|
2022-08-18 22:32:08 +01:00
|
|
|
unify(c.subType, *instantiated, constraint->scope);
|
2022-07-01 00:52:43 +01:00
|
|
|
|
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));
|
|
|
|
|
2022-09-23 20:17:25 +01:00
|
|
|
switch (c.op)
|
2022-07-01 00:52:43 +01:00
|
|
|
{
|
2022-10-14 20:48:41 +01:00
|
|
|
case AstExprUnary::Not:
|
|
|
|
{
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->booleanType);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
case AstExprUnary::Len:
|
|
|
|
{
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->numberType);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
case AstExprUnary::Minus:
|
|
|
|
{
|
|
|
|
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
|
2022-09-23 20:17:25 +01:00
|
|
|
{
|
2022-10-14 20:48:41 +01:00
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
|
2022-09-23 20:17:25 +01:00
|
|
|
return true;
|
|
|
|
}
|
2022-10-14 20:48:41 +01:00
|
|
|
break;
|
|
|
|
}
|
2022-07-01 00:52:43 +01:00
|
|
|
}
|
|
|
|
|
2022-09-23 20:17:25 +01:00
|
|
|
LUAU_ASSERT(false); // TODO metatable handling
|
2022-07-01 00:52:43 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
TypeId leftType = follow(c.leftType);
|
|
|
|
TypeId rightType = follow(c.rightType);
|
2022-08-25 22:53:50 +01:00
|
|
|
TypeId resultType = follow(c.resultType);
|
2022-07-01 00:52:43 +01:00
|
|
|
|
|
|
|
if (isBlocked(leftType) || isBlocked(rightType))
|
|
|
|
{
|
2022-08-25 22:53:50 +01:00
|
|
|
/* Compound assignments create constraints of the form
|
|
|
|
*
|
|
|
|
* A <: Binary<op, A, B>
|
|
|
|
*
|
|
|
|
* This constraint is the one that is meant to unblock A, so it doesn't
|
|
|
|
* make any sense to stop and wait for someone else to do it.
|
|
|
|
*/
|
|
|
|
if (leftType != resultType && rightType != resultType)
|
|
|
|
{
|
|
|
|
block(c.leftType, constraint);
|
|
|
|
block(c.rightType, constraint);
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-01 00:52:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isNumber(leftType))
|
|
|
|
{
|
2022-08-18 22:32:08 +01:00
|
|
|
unify(leftType, rightType, constraint->scope);
|
2022-08-25 22:53:50 +01:00
|
|
|
asMutable(resultType)->ty.emplace<BoundTypeVar>(leftType);
|
2022-07-01 00:52:43 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
if (!force)
|
|
|
|
{
|
|
|
|
if (get<FreeTypeVar>(leftType))
|
|
|
|
return block(leftType, constraint);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBlocked(leftType))
|
|
|
|
{
|
2022-09-15 23:38:17 +01:00
|
|
|
asMutable(resultType)->ty.emplace<BoundTypeVar>(errorRecoveryType());
|
2022-08-25 22:53:50 +01:00
|
|
|
// reportError(constraint->location, CannotInferBinaryOperation{c.op, std::nullopt, CannotInferBinaryOperation::Operation});
|
|
|
|
return true;
|
|
|
|
}
|
2022-07-01 00:52:43 +01:00
|
|
|
|
|
|
|
// TODO metatables, classes
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* for .. in loops can play out in a bunch of different ways depending on
|
|
|
|
* the shape of iteratee.
|
|
|
|
*
|
|
|
|
* iteratee might be:
|
|
|
|
* * (nextFn)
|
|
|
|
* * (nextFn, table)
|
|
|
|
* * (nextFn, table, firstIndex)
|
|
|
|
* * table with a metatable and __index
|
|
|
|
* * table with a metatable and __call but no __index (if the metatable has
|
|
|
|
* both, __index takes precedence)
|
|
|
|
* * table with an indexer but no __index or __call (or no metatable)
|
|
|
|
*
|
|
|
|
* To dispatch this constraint, we need first to know enough about iteratee
|
|
|
|
* to figure out which of the above shapes we are actually working with.
|
|
|
|
*
|
|
|
|
* If `force` is true and we still do not know, we must flag a warning. Type
|
|
|
|
* families are the fix for this.
|
|
|
|
*
|
|
|
|
* Since we need to know all of this stuff about the types of the iteratee,
|
|
|
|
* we have no choice but for ConstraintSolver to also be the thing that
|
|
|
|
* applies constraints to the types of the iterators.
|
|
|
|
*/
|
|
|
|
|
|
|
|
auto block_ = [&](auto&& t) {
|
|
|
|
if (force)
|
|
|
|
{
|
|
|
|
// If we haven't figured out the type of the iteratee by now,
|
|
|
|
// there's nothing we can do.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
block(t, constraint);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto [iteratorTypes, iteratorTail] = flatten(c.iterator);
|
|
|
|
if (iteratorTail)
|
|
|
|
return block_(*iteratorTail);
|
|
|
|
|
2022-09-15 23:38:17 +01:00
|
|
|
{
|
|
|
|
bool blocked = false;
|
|
|
|
for (TypeId t : iteratorTypes)
|
|
|
|
{
|
|
|
|
if (isBlocked(t))
|
|
|
|
{
|
|
|
|
block(t, constraint);
|
|
|
|
blocked = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (blocked)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
if (0 == iteratorTypes.size())
|
|
|
|
{
|
2022-09-15 23:38:17 +01:00
|
|
|
Anyification anyify{arena, constraint->scope, singletonTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
|
|
|
LUAU_ASSERT(anyified);
|
|
|
|
unify(*anyified, c.variables, constraint->scope);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId nextTy = follow(iteratorTypes[0]);
|
|
|
|
if (get<FreeTypeVar>(nextTy))
|
|
|
|
return block_(nextTy);
|
|
|
|
|
|
|
|
if (get<FunctionTypeVar>(nextTy))
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
TypeId tableTy = singletonTypes->nilType;
|
2022-09-02 00:14:03 +01:00
|
|
|
if (iteratorTypes.size() >= 2)
|
|
|
|
tableTy = iteratorTypes[1];
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
TypeId firstIndexTy = singletonTypes->nilType;
|
2022-09-02 00:14:03 +01:00
|
|
|
if (iteratorTypes.size() >= 3)
|
|
|
|
firstIndexTy = iteratorTypes[2];
|
|
|
|
|
|
|
|
return tryDispatchIterableFunction(nextTy, tableTy, firstIndexTy, c, constraint, force);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
return tryDispatchIterableTable(iteratorTypes[0], c, constraint, force);
|
|
|
|
|
|
|
|
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);
|
2022-08-11 22:01:33 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
if (target->persistent || target->owningArena != arena)
|
2022-08-11 22:01:33 +01:00
|
|
|
return true;
|
|
|
|
|
2022-06-24 02:56:00 +01:00
|
|
|
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-08-04 23:35:33 +01:00
|
|
|
struct InfiniteTypeFinder : TypeVarOnceVisitor
|
|
|
|
{
|
|
|
|
ConstraintSolver* solver;
|
|
|
|
const InstantiationSignature& signature;
|
2022-09-02 00:14:03 +01:00
|
|
|
NotNull<Scope> scope;
|
2022-08-04 23:35:33 +01:00
|
|
|
bool foundInfiniteType = false;
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
|
2022-08-04 23:35:33 +01:00
|
|
|
: solver(solver)
|
|
|
|
, signature(signature)
|
2022-09-02 00:14:03 +01:00
|
|
|
, scope(scope)
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional<TypeFun> tf =
|
|
|
|
(petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value);
|
|
|
|
|
|
|
|
if (!tf.has_value())
|
|
|
|
return true;
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->singletonTypes, *tf, petv.typeArguments, petv.packArguments);
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
|
|
|
foundInfiniteType = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct InstantiationQueuer : TypeVarOnceVisitor
|
|
|
|
{
|
|
|
|
ConstraintSolver* solver;
|
|
|
|
const InstantiationSignature& signature;
|
2022-08-18 22:32:08 +01:00
|
|
|
NotNull<Scope> scope;
|
2022-09-02 00:14:03 +01:00
|
|
|
Location location;
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver, const InstantiationSignature& signature)
|
2022-08-04 23:35:33 +01:00
|
|
|
: solver(solver)
|
|
|
|
, signature(signature)
|
2022-08-18 22:32:08 +01:00
|
|
|
, scope(scope)
|
2022-09-02 00:14:03 +01:00
|
|
|
, location(location)
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const PendingExpansionTypeVar& petv) override
|
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
|
2022-08-04 23:35:33 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
const PendingExpansionTypeVar* petv = get<PendingExpansionTypeVar>(follow(c.target));
|
|
|
|
if (!petv)
|
|
|
|
{
|
|
|
|
unblock(c.target);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto bindResult = [this, &c](TypeId result) {
|
|
|
|
asMutable(c.target)->ty.emplace<BoundTypeVar>(result);
|
|
|
|
unblock(c.target);
|
|
|
|
};
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
|
|
|
|
: constraint->scope->lookupType(petv->name.value);
|
|
|
|
|
|
|
|
if (!tf.has_value())
|
|
|
|
{
|
|
|
|
reportError(UnknownSymbol{petv->name.value, UnknownSymbol::Context::Type}, constraint->location);
|
2022-09-15 23:38:17 +01:00
|
|
|
bindResult(errorRecoveryType());
|
2022-09-02 00:14:03 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-04 23:35:33 +01:00
|
|
|
// If there are no parameters to the type function we can just use the type
|
|
|
|
// directly.
|
2022-09-02 00:14:03 +01:00
|
|
|
if (tf->typeParams.empty() && tf->typePackParams.empty())
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
bindResult(tf->type);
|
2022-08-04 23:35:33 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-08 23:14:25 +01:00
|
|
|
auto [typeArguments, packArguments] = saturateArguments(arena, singletonTypes, *tf, petv->typeArguments, petv->packArguments);
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
|
|
|
|
return itp == p.ty;
|
|
|
|
});
|
2022-08-04 23:35:33 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool samePacks =
|
|
|
|
std::equal(packArguments.begin(), packArguments.end(), tf->typePackParams.begin(), tf->typePackParams.end(), [](auto&& itp, auto&& p) {
|
2022-08-04 23:35:33 +01:00
|
|
|
return itp == p.tp;
|
|
|
|
});
|
|
|
|
|
|
|
|
// If we're instantiating the type with its generic saturatedTypeArguments we are
|
|
|
|
// performing the identity substitution. We can just short-circuit and bind
|
|
|
|
// to the TypeFun's type.
|
|
|
|
if (sameTypes && samePacks)
|
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
bindResult(tf->type);
|
2022-08-04 23:35:33 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
InstantiationSignature signature{
|
2022-09-02 00:14:03 +01:00
|
|
|
*tf,
|
2022-08-04 23:35:33 +01:00
|
|
|
typeArguments,
|
|
|
|
packArguments,
|
|
|
|
};
|
|
|
|
|
|
|
|
// If we use the same signature, we don't need to bother trying to
|
|
|
|
// instantiate the alias again, since the instantiation should be
|
|
|
|
// deterministic.
|
|
|
|
if (TypeId* cached = instantiatedAliases.find(signature))
|
|
|
|
{
|
|
|
|
bindResult(*cached);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In order to prevent infinite types from being expanded and causing us to
|
|
|
|
// cycle infinitely, we need to scan the type function for cases where we
|
|
|
|
// expand the same alias with different type saturatedTypeArguments. See
|
|
|
|
// https://github.com/Roblox/luau/pull/68 for the RFC responsible for this.
|
|
|
|
// This is a little nicer than using a recursion limit because we can catch
|
|
|
|
// the infinite expansion before actually trying to expand it.
|
2022-09-02 00:14:03 +01:00
|
|
|
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
|
|
|
itf.traverse(tf->type);
|
2022-08-04 23:35:33 +01:00
|
|
|
|
|
|
|
if (itf.foundInfiniteType)
|
|
|
|
{
|
|
|
|
// TODO (CLI-56761): Report an error.
|
2022-09-15 23:38:17 +01:00
|
|
|
bindResult(errorRecoveryType());
|
2022-08-04 23:35:33 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ApplyTypeFunction applyTypeFunction{arena};
|
|
|
|
for (size_t i = 0; i < typeArguments.size(); ++i)
|
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
applyTypeFunction.typeArguments[tf->typeParams[i].ty] = typeArguments[i];
|
2022-08-04 23:35:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < packArguments.size(); ++i)
|
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = packArguments[i];
|
2022-08-04 23:35:33 +01:00
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional<TypeId> maybeInstantiated = applyTypeFunction.substitute(tf->type);
|
2022-08-04 23:35:33 +01:00
|
|
|
// Note that ApplyTypeFunction::encounteredForwardedType is never set in
|
|
|
|
// DCR, because we do not use free types for forward-declared generic
|
|
|
|
// aliases.
|
|
|
|
|
|
|
|
if (!maybeInstantiated.has_value())
|
|
|
|
{
|
|
|
|
// TODO (CLI-56761): Report an error.
|
2022-09-15 23:38:17 +01:00
|
|
|
bindResult(errorRecoveryType());
|
2022-08-04 23:35:33 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId instantiated = *maybeInstantiated;
|
|
|
|
TypeId target = follow(instantiated);
|
2022-08-11 22:01:33 +01:00
|
|
|
|
|
|
|
if (target->persistent)
|
|
|
|
return true;
|
|
|
|
|
2022-08-04 23:35:33 +01:00
|
|
|
// Type function application will happily give us the exact same type if
|
|
|
|
// there are e.g. generic saturatedTypeArguments that go unused.
|
2022-09-02 00:14:03 +01:00
|
|
|
bool needsClone = follow(tf->type) == target;
|
2022-08-04 23:35:33 +01:00
|
|
|
// Only tables have the properties we're trying to set.
|
|
|
|
TableTypeVar* ttv = getMutableTableType(target);
|
|
|
|
|
|
|
|
if (ttv)
|
|
|
|
{
|
|
|
|
if (needsClone)
|
|
|
|
{
|
|
|
|
// Substitution::clone is a shallow clone. If this is a
|
|
|
|
// metatable type, we want to mutate its table, so we need to
|
|
|
|
// explicitly clone that table as well. If we don't, we will
|
|
|
|
// mutate another module's type surface and cause a
|
|
|
|
// use-after-free.
|
|
|
|
if (get<MetatableTypeVar>(target))
|
|
|
|
{
|
|
|
|
instantiated = applyTypeFunction.clone(target);
|
|
|
|
MetatableTypeVar* mtv = getMutable<MetatableTypeVar>(instantiated);
|
|
|
|
mtv->table = applyTypeFunction.clone(mtv->table);
|
|
|
|
ttv = getMutable<TableTypeVar>(mtv->table);
|
|
|
|
}
|
|
|
|
else if (get<TableTypeVar>(target))
|
|
|
|
{
|
|
|
|
instantiated = applyTypeFunction.clone(target);
|
|
|
|
ttv = getMutable<TableTypeVar>(instantiated);
|
|
|
|
}
|
|
|
|
|
|
|
|
target = follow(instantiated);
|
|
|
|
}
|
|
|
|
|
|
|
|
ttv->instantiatedTypeParams = typeArguments;
|
|
|
|
ttv->instantiatedTypePackParams = packArguments;
|
|
|
|
// TODO: Fill in definitionModuleName.
|
|
|
|
}
|
|
|
|
|
|
|
|
bindResult(target);
|
|
|
|
|
|
|
|
// The application is not recursive, so we need to queue up application of
|
|
|
|
// any child type function instantiations within the result in order for it
|
|
|
|
// to be complete.
|
2022-09-02 00:14:03 +01:00
|
|
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this, signature};
|
2022-08-04 23:35:33 +01:00
|
|
|
queuer.traverse(target);
|
|
|
|
|
|
|
|
instantiatedAliases[signature] = target;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
TypeId fn = follow(c.fn);
|
|
|
|
TypePackId result = follow(c.result);
|
|
|
|
|
|
|
|
if (isBlocked(c.fn))
|
|
|
|
{
|
|
|
|
return block(c.fn, constraint);
|
|
|
|
}
|
|
|
|
|
|
|
|
const FunctionTypeVar* ftv = get<FunctionTypeVar>(fn);
|
|
|
|
bool usedMagic = false;
|
|
|
|
|
|
|
|
if (ftv && ftv->dcrMagicFunction != nullptr)
|
|
|
|
{
|
2022-09-29 23:23:10 +01:00
|
|
|
usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull(this), c.callSite, c.argsPack, result});
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
2022-09-15 23:38:17 +01:00
|
|
|
if (usedMagic)
|
2022-09-02 00:14:03 +01:00
|
|
|
{
|
2022-09-15 23:38:17 +01:00
|
|
|
// There are constraints that are blocked on these constraints. If we
|
|
|
|
// are never going to even examine them, then we should not block
|
|
|
|
// anything else on them.
|
|
|
|
//
|
|
|
|
// TODO CLI-58842
|
|
|
|
#if 0
|
|
|
|
for (auto& c: c.innerConstraints)
|
|
|
|
unblock(c);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsolvedConstraints.insert(end(unsolvedConstraints), begin(c.innerConstraints), end(c.innerConstraints));
|
2022-09-02 00:14:03 +01:00
|
|
|
asMutable(c.result)->ty.emplace<FreeTypePack>(constraint->scope);
|
|
|
|
}
|
|
|
|
|
|
|
|
unblock(c.result);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-23 20:17:25 +01:00
|
|
|
bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
TypeId expectedType = follow(c.expectedType);
|
|
|
|
if (isBlocked(expectedType) || get<PendingExpansionTypeVar>(expectedType))
|
|
|
|
return block(expectedType, constraint);
|
|
|
|
|
|
|
|
TypeId bindTo = maybeSingleton(expectedType) ? c.singletonType : c.multitonType;
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(bindTo);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
TypeId subjectType = follow(c.subjectType);
|
|
|
|
|
|
|
|
if (isBlocked(subjectType) || get<PendingExpansionTypeVar>(subjectType))
|
|
|
|
return block(subjectType, constraint);
|
|
|
|
|
|
|
|
TypeId resultType = nullptr;
|
|
|
|
|
|
|
|
auto collectParts = [&](auto&& unionOrIntersection) -> std::pair<bool, std::vector<TypeId>> {
|
|
|
|
bool blocked = false;
|
|
|
|
|
|
|
|
std::vector<TypeId> parts;
|
|
|
|
for (TypeId expectedPart : unionOrIntersection)
|
|
|
|
{
|
|
|
|
expectedPart = follow(expectedPart);
|
|
|
|
if (isBlocked(expectedPart) || get<PendingExpansionTypeVar>(expectedPart))
|
|
|
|
{
|
|
|
|
blocked = true;
|
|
|
|
block(expectedPart, constraint);
|
|
|
|
}
|
|
|
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(follow(expectedPart)))
|
|
|
|
{
|
|
|
|
if (auto prop = ttv->props.find(c.prop); prop != ttv->props.end())
|
|
|
|
parts.push_back(prop->second.type);
|
|
|
|
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
|
|
|
|
parts.push_back(ttv->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {blocked, parts};
|
|
|
|
};
|
|
|
|
|
|
|
|
if (auto ttv = get<TableTypeVar>(subjectType))
|
|
|
|
{
|
|
|
|
if (auto prop = ttv->props.find(c.prop); prop != ttv->props.end())
|
|
|
|
resultType = prop->second.type;
|
|
|
|
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
|
|
|
|
resultType = ttv->indexer->indexResultType;
|
|
|
|
}
|
|
|
|
else if (auto utv = get<UnionTypeVar>(subjectType))
|
|
|
|
{
|
|
|
|
auto [blocked, parts] = collectParts(utv);
|
|
|
|
|
|
|
|
if (blocked)
|
|
|
|
return false;
|
|
|
|
else if (parts.size() == 1)
|
|
|
|
resultType = parts[0];
|
|
|
|
else if (parts.size() > 1)
|
|
|
|
resultType = arena->addType(UnionTypeVar{std::move(parts)});
|
|
|
|
else
|
|
|
|
LUAU_ASSERT(false); // parts.size() == 0
|
|
|
|
}
|
|
|
|
else if (auto itv = get<IntersectionTypeVar>(subjectType))
|
|
|
|
{
|
|
|
|
auto [blocked, parts] = collectParts(itv);
|
|
|
|
|
|
|
|
if (blocked)
|
|
|
|
return false;
|
|
|
|
else if (parts.size() == 1)
|
|
|
|
resultType = parts[0];
|
|
|
|
else if (parts.size() > 1)
|
|
|
|
resultType = arena->addType(IntersectionTypeVar{std::move(parts)});
|
|
|
|
else
|
|
|
|
LUAU_ASSERT(false); // parts.size() == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resultType)
|
|
|
|
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(resultType);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
auto block_ = [&](auto&& t) {
|
|
|
|
if (force)
|
|
|
|
{
|
|
|
|
// TODO: I believe it is the case that, if we are asked to force
|
|
|
|
// this constraint, then we can do nothing but fail. I'd like to
|
|
|
|
// find a code sample that gets here.
|
2022-09-23 20:17:25 +01:00
|
|
|
LUAU_ASSERT(false);
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
block(t, constraint);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// We may have to block here if we don't know what the iteratee type is,
|
|
|
|
// if it's a free table, if we don't know it has a metatable, and so on.
|
|
|
|
iteratorTy = follow(iteratorTy);
|
|
|
|
if (get<FreeTypeVar>(iteratorTy))
|
|
|
|
return block_(iteratorTy);
|
|
|
|
|
|
|
|
auto anyify = [&](auto ty) {
|
2022-09-08 23:14:25 +01:00
|
|
|
Anyification anyify{arena, constraint->scope, singletonTypes, &iceReporter, singletonTypes->anyType, singletonTypes->anyTypePack};
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional anyified = anyify.substitute(ty);
|
|
|
|
if (!anyified)
|
|
|
|
reportError(CodeTooComplex{}, constraint->location);
|
|
|
|
else
|
|
|
|
unify(*anyified, ty, constraint->scope);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto errorify = [&](auto ty) {
|
2022-09-15 23:38:17 +01:00
|
|
|
Anyification anyify{arena, constraint->scope, singletonTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
2022-09-02 00:14:03 +01:00
|
|
|
std::optional errorified = anyify.substitute(ty);
|
|
|
|
if (!errorified)
|
|
|
|
reportError(CodeTooComplex{}, constraint->location);
|
|
|
|
else
|
|
|
|
unify(*errorified, ty, constraint->scope);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (get<AnyTypeVar>(iteratorTy))
|
|
|
|
{
|
|
|
|
anyify(c.variables);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<ErrorTypeVar>(iteratorTy))
|
|
|
|
{
|
|
|
|
errorify(c.variables);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Irksome: I don't think we have any way to guarantee that this table
|
|
|
|
// type never has a metatable.
|
|
|
|
|
|
|
|
if (auto iteratorTable = get<TableTypeVar>(iteratorTy))
|
|
|
|
{
|
|
|
|
if (iteratorTable->state == TableState::Free)
|
|
|
|
return block_(iteratorTy);
|
|
|
|
|
|
|
|
if (iteratorTable->indexer)
|
|
|
|
{
|
|
|
|
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
|
|
|
|
unify(c.variables, expectedVariablePack, constraint->scope);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
errorify(c.variables);
|
|
|
|
}
|
2022-09-29 23:23:10 +01:00
|
|
|
else if (std::optional<TypeId> iterFn = findMetatableEntry(singletonTypes, errors, iteratorTy, "__iter", Location{}))
|
|
|
|
{
|
|
|
|
if (isBlocked(*iterFn))
|
|
|
|
{
|
|
|
|
return block(*iterFn, constraint);
|
|
|
|
}
|
|
|
|
|
|
|
|
Instantiation instantiation(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
|
|
|
|
|
|
|
if (std::optional<TypeId> instantiatedIterFn = instantiation.substitute(*iterFn))
|
|
|
|
{
|
|
|
|
if (auto iterFtv = get<FunctionTypeVar>(*instantiatedIterFn))
|
|
|
|
{
|
|
|
|
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
|
|
|
unify(iterFtv->argTypes, expectedIterArgs, constraint->scope);
|
|
|
|
|
|
|
|
std::vector<TypeId> iterRets = flatten(*arena, singletonTypes, iterFtv->retTypes, 2);
|
|
|
|
|
|
|
|
if (iterRets.size() < 1)
|
|
|
|
{
|
|
|
|
// We've done what we can; this will get reported as an
|
|
|
|
// error by the type checker.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId nextFn = iterRets[0];
|
|
|
|
TypeId table = iterRets.size() == 2 ? iterRets[1] : arena->freshType(constraint->scope);
|
|
|
|
|
|
|
|
if (std::optional<TypeId> instantiatedNextFn = instantiation.substitute(nextFn))
|
|
|
|
{
|
|
|
|
const TypeId firstIndex = arena->freshType(constraint->scope);
|
|
|
|
|
|
|
|
// nextTy : (iteratorTy, indexTy?) -> (indexTy, valueTailTy...)
|
|
|
|
const TypePackId nextArgPack = arena->addTypePack({table, arena->addType(UnionTypeVar{{firstIndex, singletonTypes->nilType}})});
|
|
|
|
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
|
|
|
|
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
|
|
|
|
|
|
|
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{nextArgPack, nextRetPack});
|
|
|
|
unify(*instantiatedNextFn, expectedNextTy, constraint->scope);
|
|
|
|
|
|
|
|
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reportError(UnificationTooComplex{}, constraint->location);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// TODO: Support __call and function overloads (what does an overload even mean for this?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reportError(UnificationTooComplex{}, constraint->location);
|
|
|
|
}
|
|
|
|
}
|
2022-09-02 00:14:03 +01:00
|
|
|
else if (auto iteratorMetatable = get<MetatableTypeVar>(iteratorTy))
|
|
|
|
{
|
|
|
|
TypeId metaTy = follow(iteratorMetatable->metatable);
|
|
|
|
if (get<FreeTypeVar>(metaTy))
|
|
|
|
return block_(metaTy);
|
|
|
|
|
2022-09-23 20:17:25 +01:00
|
|
|
LUAU_ASSERT(false);
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
errorify(c.variables);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::tryDispatchIterableFunction(
|
|
|
|
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
|
|
{
|
|
|
|
// We need to know whether or not this type is nil or not.
|
|
|
|
// If we don't know, block and reschedule ourselves.
|
|
|
|
firstIndexTy = follow(firstIndexTy);
|
|
|
|
if (get<FreeTypeVar>(firstIndexTy))
|
|
|
|
{
|
|
|
|
if (force)
|
2022-09-23 20:17:25 +01:00
|
|
|
LUAU_ASSERT(false);
|
2022-09-02 00:14:03 +01:00
|
|
|
else
|
|
|
|
block(firstIndexTy, constraint);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const TypeId firstIndex = isNil(firstIndexTy) ? arena->freshType(constraint->scope) // FIXME: Surely this should be a union (free | nil)
|
|
|
|
: firstIndexTy;
|
|
|
|
|
|
|
|
// nextTy : (tableTy, indexTy?) -> (indexTy, valueTailTy...)
|
2022-09-08 23:14:25 +01:00
|
|
|
const TypePackId nextArgPack = arena->addTypePack({tableTy, arena->addType(UnionTypeVar{{firstIndex, singletonTypes->nilType}})});
|
2022-09-02 00:14:03 +01:00
|
|
|
const TypePackId valueTailTy = arena->addTypePack(FreeTypePack{constraint->scope});
|
|
|
|
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
|
|
|
|
2022-09-29 23:23:10 +01:00
|
|
|
const TypeId expectedNextTy = arena->addType(FunctionTypeVar{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
|
2022-09-02 00:14:03 +01:00
|
|
|
unify(nextTy, expectedNextTy, constraint->scope);
|
|
|
|
|
|
|
|
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->pushBlock(constraint, target);
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
if (FFlag::DebugLuauLogSolver)
|
2022-09-02 00:14:03 +01:00
|
|
|
printf("block Constraint %s on\t%s\n", toString(*target, opts).c_str(), toString(*constraint, opts).c_str());
|
2022-09-08 23:14:25 +01:00
|
|
|
|
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
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->pushBlock(constraint, target);
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
if (FFlag::DebugLuauLogSolver)
|
2022-09-02 00:14:03 +01:00
|
|
|
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
|
2022-09-08 23:14:25 +01:00
|
|
|
|
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
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->pushBlock(constraint, target);
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
if (FFlag::DebugLuauLogSolver)
|
2022-09-02 00:14:03 +01:00
|
|
|
printf("block TypeId %s on\t%s\n", toString(target, opts).c_str(), toString(*constraint, opts).c_str());
|
2022-09-08 23:14:25 +01:00
|
|
|
|
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-09-23 20:17:25 +01:00
|
|
|
struct Blocker : TypeVarOnceVisitor
|
|
|
|
{
|
|
|
|
NotNull<ConstraintSolver> solver;
|
|
|
|
NotNull<const Constraint> constraint;
|
|
|
|
|
|
|
|
bool blocked = false;
|
|
|
|
|
|
|
|
explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint)
|
|
|
|
: solver(solver)
|
|
|
|
, constraint(constraint)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const BlockedTypeVar&)
|
|
|
|
{
|
|
|
|
blocked = true;
|
|
|
|
solver->block(ty, constraint);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const PendingExpansionTypeVar&)
|
|
|
|
{
|
|
|
|
blocked = true;
|
|
|
|
solver->block(ty, constraint);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
bool ConstraintSolver::recursiveBlock(TypeId target, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
Blocker blocker{NotNull{this}, constraint};
|
|
|
|
blocker.traverse(target);
|
|
|
|
return !blocker.blocked;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ConstraintSolver::recursiveBlock(TypePackId pack, NotNull<const Constraint> constraint)
|
|
|
|
{
|
|
|
|
Blocker blocker{NotNull{this}, constraint};
|
|
|
|
blocker.traverse(pack);
|
|
|
|
return !blocker.blocked;
|
|
|
|
}
|
|
|
|
|
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];
|
2022-08-25 22:53:50 +01:00
|
|
|
if (FFlag::DebugLuauLogSolver)
|
2022-09-02 00:14:03 +01:00
|
|
|
printf("Unblocking count=%d\t%s\n", int(count), toString(*unblockedConstraint, opts).c_str());
|
2022-08-25 22:53:50 +01:00
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
// 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
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->popBlock(progressed);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock(TypeId progressed)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->popBlock(progressed);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock(TypePackId progressed)
|
|
|
|
{
|
2022-09-08 23:14:25 +01:00
|
|
|
if (FFlag::DebugLuauLogSolverToJson)
|
|
|
|
logger->popBlock(progressed);
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
return unblock_(progressed);
|
|
|
|
}
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
void ConstraintSolver::unblock(const std::vector<TypeId>& types)
|
|
|
|
{
|
|
|
|
for (TypeId t : types)
|
|
|
|
unblock(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::unblock(const std::vector<TypePackId>& packs)
|
|
|
|
{
|
|
|
|
for (TypePackId t : packs)
|
|
|
|
unblock(t);
|
|
|
|
}
|
|
|
|
|
2022-06-17 02:05:14 +01:00
|
|
|
bool ConstraintSolver::isBlocked(TypeId ty)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-08-04 23:35:33 +01:00
|
|
|
return nullptr != get<BlockedTypeVar>(follow(ty)) || nullptr != get<PendingExpansionTypeVar>(follow(ty));
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
bool ConstraintSolver::isBlocked(TypePackId tp)
|
|
|
|
{
|
|
|
|
return nullptr != get<BlockedTypePack>(follow(tp));
|
|
|
|
}
|
|
|
|
|
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-08-18 22:32:08 +01:00
|
|
|
void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
2022-10-07 01:23:29 +01:00
|
|
|
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
2022-09-29 23:23:10 +01:00
|
|
|
u.useScopes = true;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
u.tryUnify(subType, superType);
|
2022-08-25 22:53:50 +01:00
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
if (!u.errors.empty())
|
|
|
|
{
|
2022-09-15 23:38:17 +01:00
|
|
|
TypeId errorType = errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
u.tryUnify(subType, errorType);
|
|
|
|
u.tryUnify(superType, errorType);
|
|
|
|
}
|
|
|
|
|
2022-08-25 22:53:50 +01:00
|
|
|
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
u.log.commit();
|
2022-08-25 22:53:50 +01:00
|
|
|
|
|
|
|
unblock(changedTypes);
|
|
|
|
unblock(changedPacks);
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-08-18 22:32:08 +01:00
|
|
|
void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope)
|
2022-06-03 23:15:45 +01:00
|
|
|
{
|
|
|
|
UnifierSharedState sharedState{&iceReporter};
|
2022-10-07 01:23:29 +01:00
|
|
|
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
2022-09-29 23:23:10 +01:00
|
|
|
u.useScopes = true;
|
2022-06-03 23:15:45 +01:00
|
|
|
|
|
|
|
u.tryUnify(subPack, superPack);
|
2022-08-25 22:53:50 +01:00
|
|
|
|
|
|
|
const auto [changedTypes, changedPacks] = u.log.getChanges();
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
u.log.commit();
|
2022-08-25 22:53:50 +01:00
|
|
|
|
|
|
|
unblock(changedTypes);
|
|
|
|
unblock(changedPacks);
|
2022-06-03 23:15:45 +01:00
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
void ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
|
2022-08-04 23:35:33 +01:00
|
|
|
{
|
2022-09-02 00:14:03 +01:00
|
|
|
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
|
2022-08-04 23:35:33 +01:00
|
|
|
NotNull<Constraint> borrow = NotNull(c.get());
|
|
|
|
solverConstraints.push_back(std::move(c));
|
|
|
|
unsolvedConstraints.push_back(borrow);
|
|
|
|
}
|
|
|
|
|
2022-09-02 00:14:03 +01:00
|
|
|
TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location)
|
|
|
|
{
|
|
|
|
if (info.name.empty())
|
|
|
|
{
|
|
|
|
reportError(UnknownRequire{}, location);
|
2022-09-15 23:38:17 +01:00
|
|
|
return errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string humanReadableName = moduleResolver->getHumanReadableModuleName(info.name);
|
|
|
|
|
|
|
|
for (const auto& [location, path] : requireCycles)
|
|
|
|
{
|
|
|
|
if (!path.empty() && path.front() == humanReadableName)
|
2022-09-08 23:14:25 +01:00
|
|
|
return singletonTypes->anyType;
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ModulePtr module = moduleResolver->getModule(info.name);
|
|
|
|
if (!module)
|
|
|
|
{
|
|
|
|
if (!moduleResolver->moduleExists(info.name) && !info.optional)
|
|
|
|
reportError(UnknownRequire{humanReadableName}, location);
|
|
|
|
|
2022-09-15 23:38:17 +01:00
|
|
|
return errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (module->type != SourceCode::Type::Module)
|
|
|
|
{
|
|
|
|
reportError(IllegalRequire{humanReadableName, "Module is not a ModuleScript. It cannot be required."}, location);
|
2022-09-15 23:38:17 +01:00
|
|
|
return errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId modulePack = module->getModuleScope()->returnType;
|
|
|
|
if (get<Unifiable::Error>(modulePack))
|
2022-09-15 23:38:17 +01:00
|
|
|
return errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
|
|
|
|
std::optional<TypeId> moduleType = first(modulePack);
|
|
|
|
if (!moduleType)
|
|
|
|
{
|
|
|
|
reportError(IllegalRequire{humanReadableName, "Module does not return exactly 1 value. It cannot be required."}, location);
|
2022-09-15 23:38:17 +01:00
|
|
|
return errorRecoveryType();
|
2022-09-02 00:14:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return *moduleType;
|
|
|
|
}
|
|
|
|
|
2022-08-18 22:32:08 +01:00
|
|
|
void ConstraintSolver::reportError(TypeErrorData&& data, const Location& location)
|
|
|
|
{
|
|
|
|
errors.emplace_back(location, std::move(data));
|
2022-09-02 00:14:03 +01:00
|
|
|
errors.back().moduleName = currentModuleName;
|
2022-08-18 22:32:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConstraintSolver::reportError(TypeError e)
|
|
|
|
{
|
|
|
|
errors.emplace_back(std::move(e));
|
2022-09-02 00:14:03 +01:00
|
|
|
errors.back().moduleName = currentModuleName;
|
2022-08-18 22:32:08 +01:00
|
|
|
}
|
|
|
|
|
2022-09-15 23:38:17 +01:00
|
|
|
TypeId ConstraintSolver::errorRecoveryType() const
|
|
|
|
{
|
|
|
|
return singletonTypes->errorRecoveryType();
|
|
|
|
}
|
|
|
|
|
|
|
|
TypePackId ConstraintSolver::errorRecoveryTypePack() const
|
|
|
|
{
|
|
|
|
return singletonTypes->errorRecoveryTypePack();
|
|
|
|
}
|
|
|
|
|
2022-06-03 23:15:45 +01:00
|
|
|
} // namespace Luau
|