2023-08-04 20:18:54 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
|
|
|
|
#include "Luau/Unifier2.h"
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
#include "Luau/Instantiation.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/Scope.h"
|
|
|
|
#include "Luau/Simplify.h"
|
|
|
|
#include "Luau/Substitution.h"
|
|
|
|
#include "Luau/ToString.h"
|
|
|
|
#include "Luau/TxnLog.h"
|
|
|
|
#include "Luau/Type.h"
|
|
|
|
#include "Luau/TypeArena.h"
|
2023-09-22 20:12:15 +01:00
|
|
|
#include "Luau/TypeCheckLimits.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/TypeUtils.h"
|
|
|
|
#include "Luau/VisitType.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
2023-09-22 20:12:15 +01:00
|
|
|
#include <optional>
|
2023-08-04 20:18:54 +01:00
|
|
|
#include <unordered_set>
|
|
|
|
|
|
|
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
|
2023-08-04 20:18:54 +01:00
|
|
|
: arena(arena)
|
|
|
|
, builtinTypes(builtinTypes)
|
2023-09-22 20:12:15 +01:00
|
|
|
, scope(scope)
|
2023-08-04 20:18:54 +01:00
|
|
|
, ice(ice)
|
2023-09-22 20:12:15 +01:00
|
|
|
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
|
2023-08-04 20:18:54 +01:00
|
|
|
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|
|
|
{
|
|
|
|
subTy = follow(subTy);
|
|
|
|
superTy = follow(superTy);
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
if (seenTypePairings.contains({subTy, superTy}))
|
|
|
|
return true;
|
|
|
|
seenTypePairings.insert({subTy, superTy});
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
if (subTy == superTy)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
FreeType* subFree = getMutable<FreeType>(subTy);
|
|
|
|
FreeType* superFree = getMutable<FreeType>(superTy);
|
|
|
|
|
|
|
|
if (subFree)
|
|
|
|
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
|
|
|
|
|
|
|
|
if (superFree)
|
|
|
|
superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
|
|
|
|
|
|
|
|
if (subFree || superFree)
|
|
|
|
return true;
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
auto subFn = get<FunctionType>(subTy);
|
|
|
|
auto superFn = get<FunctionType>(superTy);
|
2023-08-04 20:18:54 +01:00
|
|
|
if (subFn && superFn)
|
2023-09-22 20:12:15 +01:00
|
|
|
return unify(subTy, superFn);
|
|
|
|
|
|
|
|
auto subUnion = get<UnionType>(subTy);
|
|
|
|
auto superUnion = get<UnionType>(superTy);
|
|
|
|
if (subUnion)
|
|
|
|
return unify(subUnion, superTy);
|
|
|
|
else if (superUnion)
|
|
|
|
return unify(subTy, superUnion);
|
|
|
|
|
|
|
|
auto subIntersection = get<IntersectionType>(subTy);
|
|
|
|
auto superIntersection = get<IntersectionType>(superTy);
|
|
|
|
if (subIntersection)
|
|
|
|
return unify(subIntersection, superTy);
|
|
|
|
else if (superIntersection)
|
|
|
|
return unify(subTy, superIntersection);
|
|
|
|
|
|
|
|
auto subNever = get<NeverType>(subTy);
|
|
|
|
auto superNever = get<NeverType>(superTy);
|
|
|
|
if (subNever && superNever)
|
|
|
|
return true;
|
|
|
|
else if (subNever && superFn)
|
|
|
|
{
|
|
|
|
// If `never` is the subtype, then we can propagate that inward.
|
|
|
|
bool argResult = unify(superFn->argTypes, builtinTypes->neverTypePack);
|
|
|
|
bool retResult = unify(builtinTypes->neverTypePack, superFn->retTypes);
|
|
|
|
return argResult && retResult;
|
|
|
|
}
|
|
|
|
else if (subFn && superNever)
|
|
|
|
{
|
|
|
|
// If `never` is the supertype, then we can propagate that inward.
|
|
|
|
bool argResult = unify(builtinTypes->neverTypePack, subFn->argTypes);
|
|
|
|
bool retResult = unify(subFn->retTypes, builtinTypes->neverTypePack);
|
|
|
|
return argResult && retResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subAny = get<AnyType>(subTy);
|
|
|
|
auto superAny = get<AnyType>(superTy);
|
|
|
|
if (subAny && superAny)
|
|
|
|
return true;
|
|
|
|
else if (subAny && superFn)
|
|
|
|
{
|
|
|
|
// If `any` is the subtype, then we can propagate that inward.
|
|
|
|
bool argResult = unify(superFn->argTypes, builtinTypes->anyTypePack);
|
|
|
|
bool retResult = unify(builtinTypes->anyTypePack, superFn->retTypes);
|
|
|
|
return argResult && retResult;
|
|
|
|
}
|
|
|
|
else if (subFn && superAny)
|
2023-08-04 20:18:54 +01:00
|
|
|
{
|
2023-09-22 20:12:15 +01:00
|
|
|
// If `any` is the supertype, then we can propagate that inward.
|
|
|
|
bool argResult = unify(builtinTypes->anyTypePack, subFn->argTypes);
|
|
|
|
bool retResult = unify(subFn->retTypes, builtinTypes->anyTypePack);
|
2023-08-04 20:18:54 +01:00
|
|
|
return argResult && retResult;
|
|
|
|
}
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
auto subTable = get<TableType>(subTy);
|
|
|
|
auto superTable = get<TableType>(superTy);
|
|
|
|
if (subTable && superTable)
|
|
|
|
{
|
|
|
|
// `boundTo` works like a bound type, and therefore we'd replace it
|
|
|
|
// with the `boundTo` and try unification again.
|
|
|
|
//
|
|
|
|
// However, these pointers should have been chased already by follow().
|
|
|
|
LUAU_ASSERT(!subTable->boundTo);
|
|
|
|
LUAU_ASSERT(!superTable->boundTo);
|
|
|
|
|
|
|
|
return unify(subTable, superTable);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subMetatable = get<MetatableType>(subTy);
|
|
|
|
auto superMetatable = get<MetatableType>(superTy);
|
|
|
|
if (subMetatable && superMetatable)
|
|
|
|
return unify(subMetatable, superMetatable);
|
|
|
|
else if (subMetatable) // if we only have one metatable, unify with the inner table
|
|
|
|
return unify(subMetatable->table, superTy);
|
|
|
|
else if (superMetatable) // if we only have one metatable, unify with the inner table
|
|
|
|
return unify(subTy, superMetatable->table);
|
|
|
|
|
|
|
|
auto [subNegation, superNegation] = get2<NegationType, NegationType>(subTy, superTy);
|
|
|
|
if (subNegation && superNegation)
|
|
|
|
return unify(subNegation->ty, superNegation->ty);
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
// The unification failed, but we're not doing type checking.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) {
|
|
|
|
const FunctionType* subFn = get<FunctionType>(subTy);
|
|
|
|
|
|
|
|
bool shouldInstantiate =
|
|
|
|
(superFn->generics.empty() && !subFn->generics.empty()) || (superFn->genericPacks.empty() && !subFn->genericPacks.empty());
|
|
|
|
|
|
|
|
if (shouldInstantiate)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
|
|
|
|
subFn = get<FunctionType>(*instantiated);
|
|
|
|
|
|
|
|
LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
|
|
|
|
}
|
|
|
|
|
|
|
|
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
|
|
|
bool retResult = unify(subFn->retTypes, superFn->retTypes);
|
|
|
|
return argResult && retResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(const UnionType* subUnion, TypeId superTy)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
// if the occurs check fails for any option, it fails overall
|
|
|
|
for (auto subOption : subUnion->options)
|
|
|
|
result &= unify(subOption, superTy);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(TypeId subTy, const UnionType* superUnion)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
// if the occurs check fails for any option, it fails overall
|
|
|
|
for (auto superOption : superUnion->options)
|
|
|
|
result &= unify(subTy, superOption);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(const IntersectionType* subIntersection, TypeId superTy)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
// if the occurs check fails for any part, it fails overall
|
|
|
|
for (auto subPart : subIntersection->parts)
|
|
|
|
result &= unify(subPart, superTy);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(TypeId subTy, const IntersectionType* superIntersection)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
// if the occurs check fails for any part, it fails overall
|
|
|
|
for (auto superPart : superIntersection->parts)
|
|
|
|
result &= unify(subTy, superPart);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(const TableType* subTable, const TableType* superTable)
|
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
// It suffices to only check one direction of properties since we'll only ever have work to do during unification
|
|
|
|
// if the property is present in both table types.
|
|
|
|
for (const auto& [propName, subProp] : subTable->props)
|
|
|
|
{
|
|
|
|
auto superPropOpt = superTable->props.find(propName);
|
|
|
|
|
|
|
|
if (superPropOpt != superTable->props.end())
|
|
|
|
result &= unify(subProp.type(), superPropOpt->second.type());
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subTypeParamsIter = subTable->instantiatedTypeParams.begin();
|
|
|
|
auto superTypeParamsIter = superTable->instantiatedTypeParams.begin();
|
|
|
|
|
|
|
|
while (subTypeParamsIter != subTable->instantiatedTypeParams.end() && superTypeParamsIter != superTable->instantiatedTypeParams.end())
|
|
|
|
{
|
|
|
|
result &= unify(*subTypeParamsIter, *superTypeParamsIter);
|
|
|
|
|
|
|
|
subTypeParamsIter++;
|
|
|
|
superTypeParamsIter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subTypePackParamsIter = subTable->instantiatedTypePackParams.begin();
|
|
|
|
auto superTypePackParamsIter = superTable->instantiatedTypePackParams.begin();
|
|
|
|
|
|
|
|
while (subTypePackParamsIter != subTable->instantiatedTypePackParams.end() &&
|
|
|
|
superTypePackParamsIter != superTable->instantiatedTypePackParams.end())
|
|
|
|
{
|
|
|
|
result &= unify(*subTypePackParamsIter, *superTypePackParamsIter);
|
|
|
|
|
|
|
|
subTypePackParamsIter++;
|
|
|
|
superTypePackParamsIter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subTable->selfTy && superTable->selfTy)
|
|
|
|
result &= unify(*subTable->selfTy, *superTable->selfTy);
|
|
|
|
|
|
|
|
if (subTable->indexer && superTable->indexer)
|
|
|
|
{
|
|
|
|
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
|
|
|
|
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier2::unify(const MetatableType* subMetatable, const MetatableType* superMetatable)
|
|
|
|
{
|
|
|
|
return unify(subMetatable->metatable, superMetatable->metatable) && unify(subMetatable->table, superMetatable->table);
|
|
|
|
}
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
// FIXME? This should probably return an ErrorVec or an optional<TypeError>
|
|
|
|
// rather than a boolean to signal an occurs check failure.
|
|
|
|
bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|
|
|
{
|
|
|
|
subTp = follow(subTp);
|
|
|
|
superTp = follow(superTp);
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
if (seenTypePackPairings.contains({subTp, superTp}))
|
|
|
|
return true;
|
|
|
|
seenTypePackPairings.insert({subTp, superTp});
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
|
|
|
const FreeTypePack* superFree = get<FreeTypePack>(superTp);
|
|
|
|
|
|
|
|
if (subFree)
|
|
|
|
{
|
|
|
|
DenseHashSet<TypePackId> seen{nullptr};
|
|
|
|
if (OccursCheckResult::Fail == occursCheck(seen, subTp, superTp))
|
|
|
|
{
|
|
|
|
asMutable(subTp)->ty.emplace<BoundTypePack>(builtinTypes->errorRecoveryTypePack());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
asMutable(subTp)->ty.emplace<BoundTypePack>(superTp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (superFree)
|
|
|
|
{
|
|
|
|
DenseHashSet<TypePackId> seen{nullptr};
|
|
|
|
if (OccursCheckResult::Fail == occursCheck(seen, superTp, subTp))
|
|
|
|
{
|
|
|
|
asMutable(superTp)->ty.emplace<BoundTypePack>(builtinTypes->errorRecoveryTypePack());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
asMutable(superTp)->ty.emplace<BoundTypePack>(subTp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size());
|
2023-08-04 20:18:54 +01:00
|
|
|
|
|
|
|
auto [subTypes, subTail] = extendTypePack(*arena, builtinTypes, subTp, maxLength);
|
|
|
|
auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength);
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
// right-pad the subpack with nils if `superPack` is larger since that's what a function call does
|
|
|
|
if (subTypes.size() < maxLength)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i <= maxLength - subTypes.size(); i++)
|
|
|
|
subTypes.push_back(builtinTypes->nilType);
|
|
|
|
}
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
if (subTypes.size() < maxLength || superTypes.size() < maxLength)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < maxLength; ++i)
|
|
|
|
unify(subTypes[i], superTypes[i]);
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
if (!subTail || !superTail)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
TypePackId followedSubTail = follow(*subTail);
|
|
|
|
TypePackId followedSuperTail = follow(*superTail);
|
|
|
|
|
|
|
|
if (get<FreeTypePack>(followedSubTail) || get<FreeTypePack>(followedSuperTail))
|
|
|
|
return unify(followedSubTail, followedSuperTail);
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct FreeTypeSearcher : TypeVisitor
|
|
|
|
{
|
|
|
|
NotNull<Scope> scope;
|
|
|
|
|
|
|
|
explicit FreeTypeSearcher(NotNull<Scope> scope)
|
|
|
|
: TypeVisitor(/*skipBoundTypes*/ true)
|
|
|
|
, scope(scope)
|
2023-09-15 18:26:59 +01:00
|
|
|
{
|
|
|
|
}
|
2023-08-04 20:18:54 +01:00
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
Positive,
|
|
|
|
Negative
|
|
|
|
} polarity = Positive;
|
2023-08-04 20:18:54 +01:00
|
|
|
|
|
|
|
void flip()
|
|
|
|
{
|
|
|
|
switch (polarity)
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
case Positive:
|
|
|
|
polarity = Negative;
|
|
|
|
break;
|
|
|
|
case Negative:
|
|
|
|
polarity = Positive;
|
|
|
|
break;
|
2023-08-04 20:18:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
DenseHashMap<TypeId, size_t> negativeTypes{0};
|
|
|
|
DenseHashMap<TypeId, size_t> positiveTypes{0};
|
2023-08-04 20:18:54 +01:00
|
|
|
|
|
|
|
bool visit(TypeId ty) override
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(ty);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const FreeType& ft) override
|
|
|
|
{
|
|
|
|
if (!subsumes(scope, ft.scope))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
switch (polarity)
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
case Positive:
|
2023-09-22 20:12:15 +01:00
|
|
|
positiveTypes[ty]++;
|
2023-09-15 18:26:59 +01:00
|
|
|
break;
|
|
|
|
case Negative:
|
2023-09-22 20:12:15 +01:00
|
|
|
negativeTypes[ty]++;
|
2023-09-15 18:26:59 +01:00
|
|
|
break;
|
2023-08-04 20:18:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
bool visit(TypeId ty, const TableType& tt) override
|
|
|
|
{
|
|
|
|
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
|
|
|
|
{
|
|
|
|
switch (polarity)
|
|
|
|
{
|
|
|
|
case Positive:
|
|
|
|
positiveTypes[ty]++;
|
|
|
|
break;
|
|
|
|
case Negative:
|
|
|
|
negativeTypes[ty]++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-04 20:18:54 +01:00
|
|
|
bool visit(TypeId ty, const FunctionType& ft) override
|
|
|
|
{
|
|
|
|
flip();
|
|
|
|
traverse(ft.argTypes);
|
|
|
|
flip();
|
|
|
|
|
|
|
|
traverse(ft.retTypes);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct MutatingGeneralizer : TypeOnceVisitor
|
|
|
|
{
|
|
|
|
NotNull<BuiltinTypes> builtinTypes;
|
|
|
|
|
|
|
|
NotNull<Scope> scope;
|
2023-09-22 20:12:15 +01:00
|
|
|
DenseHashMap<TypeId, size_t> positiveTypes;
|
|
|
|
DenseHashMap<TypeId, size_t> negativeTypes;
|
2023-08-04 20:18:54 +01:00
|
|
|
std::vector<TypeId> generics;
|
2023-09-22 20:12:15 +01:00
|
|
|
std::vector<TypePackId> genericPacks;
|
2023-08-04 20:18:54 +01:00
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
bool isWithinFunction = false;
|
|
|
|
|
|
|
|
MutatingGeneralizer(
|
2023-09-22 20:12:15 +01:00
|
|
|
NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, DenseHashMap<TypeId, size_t> positiveTypes, DenseHashMap<TypeId, size_t> negativeTypes)
|
2023-08-04 20:18:54 +01:00
|
|
|
: TypeOnceVisitor(/* skipBoundTypes */ true)
|
|
|
|
, builtinTypes(builtinTypes)
|
|
|
|
, scope(scope)
|
|
|
|
, positiveTypes(std::move(positiveTypes))
|
|
|
|
, negativeTypes(std::move(negativeTypes))
|
2023-09-15 18:26:59 +01:00
|
|
|
{
|
|
|
|
}
|
2023-08-04 20:18:54 +01:00
|
|
|
|
|
|
|
static void replace(DenseHashSet<TypeId>& seen, TypeId haystack, TypeId needle, TypeId replacement)
|
|
|
|
{
|
|
|
|
haystack = follow(haystack);
|
|
|
|
|
|
|
|
if (seen.find(haystack))
|
|
|
|
return;
|
|
|
|
seen.insert(haystack);
|
|
|
|
|
|
|
|
std::vector<TypeId>* parts = nullptr;
|
|
|
|
if (UnionType* ut = getMutable<UnionType>(haystack))
|
|
|
|
parts = &ut->options;
|
|
|
|
else if (IntersectionType* it = getMutable<IntersectionType>(needle))
|
|
|
|
parts = &it->parts;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
LUAU_ASSERT(parts);
|
|
|
|
|
|
|
|
for (TypeId& option : *parts)
|
|
|
|
{
|
|
|
|
// FIXME: I bet this function has reentrancy problems
|
|
|
|
option = follow(option);
|
|
|
|
if (option == needle)
|
|
|
|
option = replacement;
|
|
|
|
|
|
|
|
// TODO seen set
|
|
|
|
else if (get<UnionType>(option))
|
|
|
|
replace(seen, option, needle, haystack);
|
|
|
|
else if (get<IntersectionType>(option))
|
|
|
|
replace(seen, option, needle, haystack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
bool visit(TypeId ty, const FunctionType& ft) override
|
|
|
|
{
|
|
|
|
const bool oldValue = isWithinFunction;
|
|
|
|
|
|
|
|
isWithinFunction = true;
|
|
|
|
|
|
|
|
traverse(ft.argTypes);
|
|
|
|
traverse(ft.retTypes);
|
|
|
|
|
|
|
|
isWithinFunction = oldValue;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const FreeType&) override
|
2023-08-04 20:18:54 +01:00
|
|
|
{
|
|
|
|
const FreeType* ft = get<FreeType>(ty);
|
|
|
|
LUAU_ASSERT(ft);
|
|
|
|
|
|
|
|
traverse(ft->lowerBound);
|
|
|
|
traverse(ft->upperBound);
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
// It is possible for the above traverse() calls to cause ty to be
|
|
|
|
// transmuted. We must reaquire ft if this happens.
|
2023-08-04 20:18:54 +01:00
|
|
|
ty = follow(ty);
|
|
|
|
ft = get<FreeType>(ty);
|
|
|
|
if (!ft)
|
|
|
|
return false;
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
const bool positiveCount = getCount(positiveTypes, ty);
|
|
|
|
const bool negativeCount = getCount(negativeTypes, ty);
|
2023-08-04 20:18:54 +01:00
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
if (!positiveCount && !negativeCount)
|
2023-08-04 20:18:54 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
const bool hasLowerBound = !get<NeverType>(follow(ft->lowerBound));
|
|
|
|
const bool hasUpperBound = !get<UnknownType>(follow(ft->upperBound));
|
|
|
|
|
|
|
|
DenseHashSet<TypeId> seen{nullptr};
|
|
|
|
seen.insert(ty);
|
|
|
|
|
|
|
|
if (!hasLowerBound && !hasUpperBound)
|
|
|
|
{
|
2023-09-22 20:12:15 +01:00
|
|
|
if (!isWithinFunction || (positiveCount + negativeCount == 1))
|
|
|
|
emplaceType<BoundType>(asMutable(ty), builtinTypes->unknownType);
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
{
|
|
|
|
emplaceType<GenericType>(asMutable(ty), scope);
|
|
|
|
generics.push_back(ty);
|
|
|
|
}
|
2023-08-04 20:18:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// It is possible that this free type has other free types in its upper
|
|
|
|
// or lower bounds. If this is the case, we must replace those
|
|
|
|
// references with never (for the lower bound) or unknown (for the upper
|
|
|
|
// bound).
|
|
|
|
//
|
|
|
|
// If we do not do this, we get tautological bounds like a <: a <: unknown.
|
2023-09-22 20:12:15 +01:00
|
|
|
else if (positiveCount && !hasUpperBound)
|
2023-08-04 20:18:54 +01:00
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
TypeId lb = follow(ft->lowerBound);
|
|
|
|
if (FreeType* lowerFree = getMutable<FreeType>(lb); lowerFree && lowerFree->upperBound == ty)
|
2023-08-04 20:18:54 +01:00
|
|
|
lowerFree->upperBound = builtinTypes->unknownType;
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
{
|
|
|
|
DenseHashSet<TypeId> replaceSeen{nullptr};
|
|
|
|
replace(replaceSeen, lb, ty, builtinTypes->unknownType);
|
|
|
|
}
|
|
|
|
emplaceType<BoundType>(asMutable(ty), lb);
|
2023-08-04 20:18:54 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-15 18:26:59 +01:00
|
|
|
TypeId ub = follow(ft->upperBound);
|
|
|
|
if (FreeType* upperFree = getMutable<FreeType>(ub); upperFree && upperFree->lowerBound == ty)
|
2023-08-04 20:18:54 +01:00
|
|
|
upperFree->lowerBound = builtinTypes->neverType;
|
|
|
|
else
|
2023-09-15 18:26:59 +01:00
|
|
|
{
|
|
|
|
DenseHashSet<TypeId> replaceSeen{nullptr};
|
|
|
|
replace(replaceSeen, ub, ty, builtinTypes->neverType);
|
|
|
|
}
|
|
|
|
emplaceType<BoundType>(asMutable(ty), ub);
|
2023-08-04 20:18:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2023-09-22 20:12:15 +01:00
|
|
|
|
|
|
|
size_t getCount(const DenseHashMap<TypeId, size_t>& map, TypeId ty)
|
|
|
|
{
|
|
|
|
if (const size_t* count = map.find(ty))
|
|
|
|
return *count;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const TableType&) override
|
|
|
|
{
|
|
|
|
const size_t positiveCount = getCount(positiveTypes, ty);
|
|
|
|
const size_t negativeCount = getCount(negativeTypes, ty);
|
|
|
|
|
|
|
|
// FIXME: Free tables should probably just be replaced by upper bounds on free types.
|
|
|
|
//
|
|
|
|
// eg never <: 'a <: {x: number} & {z: boolean}
|
|
|
|
|
|
|
|
if (!positiveCount && !negativeCount)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
TableType* tt = getMutable<TableType>(ty);
|
|
|
|
LUAU_ASSERT(tt);
|
|
|
|
|
|
|
|
// We only unseal tables if they occur within function argument or
|
|
|
|
// return lists. In principle, we could always seal tables when
|
|
|
|
// generalizing, but that would mean that we'd lose the ability to
|
|
|
|
// report the existence of unsealed tables via things like hovertype.
|
|
|
|
if (tt->state == TableState::Unsealed && !isWithinFunction)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
tt->state = TableState::Sealed;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypePackId tp, const FreeTypePack& ftp) override
|
|
|
|
{
|
|
|
|
if (!subsumes(scope, ftp.scope))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
asMutable(tp)->ty.emplace<GenericTypePack>(scope);
|
|
|
|
|
|
|
|
genericPacks.push_back(tp);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2023-08-04 20:18:54 +01:00
|
|
|
};
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
std::optional<TypeId> Unifier2::generalize(TypeId ty)
|
2023-08-04 20:18:54 +01:00
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
|
|
|
|
if (ty->owningArena != arena)
|
|
|
|
return ty;
|
|
|
|
|
|
|
|
if (ty->persistent)
|
|
|
|
return ty;
|
|
|
|
|
|
|
|
if (const FunctionType* ft = get<FunctionType>(ty); ft && (!ft->generics.empty() || !ft->genericPacks.empty()))
|
|
|
|
return ty;
|
|
|
|
|
|
|
|
FreeTypeSearcher fts{scope};
|
|
|
|
fts.traverse(ty);
|
|
|
|
|
|
|
|
MutatingGeneralizer gen{builtinTypes, scope, std::move(fts.positiveTypes), std::move(fts.negativeTypes)};
|
|
|
|
|
|
|
|
gen.traverse(ty);
|
|
|
|
|
|
|
|
std::optional<TypeId> res = ty;
|
|
|
|
|
|
|
|
FunctionType* ftv = getMutable<FunctionType>(follow(*res));
|
|
|
|
if (ftv)
|
2023-09-22 20:12:15 +01:00
|
|
|
{
|
2023-08-04 20:18:54 +01:00
|
|
|
ftv->generics = std::move(gen.generics);
|
2023-09-22 20:12:15 +01:00
|
|
|
ftv->genericPacks = std::move(gen.genericPacks);
|
|
|
|
}
|
2023-08-04 20:18:54 +01:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
|
|
|
{
|
|
|
|
left = follow(left);
|
|
|
|
right = follow(right);
|
|
|
|
|
|
|
|
return simplifyUnion(builtinTypes, arena, left, right).result;
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId Unifier2::mkIntersection(TypeId left, TypeId right)
|
|
|
|
{
|
|
|
|
left = follow(left);
|
|
|
|
right = follow(right);
|
|
|
|
|
|
|
|
return simplifyIntersection(builtinTypes, arena, left, right).result;
|
|
|
|
}
|
|
|
|
|
|
|
|
OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
|
|
|
{
|
|
|
|
needle = follow(needle);
|
|
|
|
haystack = follow(haystack);
|
|
|
|
|
|
|
|
if (seen.find(haystack))
|
|
|
|
return OccursCheckResult::Pass;
|
|
|
|
|
|
|
|
seen.insert(haystack);
|
|
|
|
|
|
|
|
if (getMutable<ErrorTypePack>(needle))
|
|
|
|
return OccursCheckResult::Pass;
|
|
|
|
|
|
|
|
if (!getMutable<FreeTypePack>(needle))
|
|
|
|
ice->ice("Expected needle pack to be free");
|
|
|
|
|
|
|
|
RecursionLimiter _ra(&recursionCount, recursionLimit);
|
|
|
|
|
|
|
|
while (!getMutable<Unifiable::Error>(haystack))
|
|
|
|
{
|
|
|
|
if (needle == haystack)
|
|
|
|
return OccursCheckResult::Fail;
|
|
|
|
|
|
|
|
if (auto a = get<TypePack>(haystack); a && a->tail)
|
|
|
|
{
|
|
|
|
haystack = follow(*a->tail);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OccursCheckResult::Pass;
|
|
|
|
}
|
|
|
|
|
2023-09-15 18:26:59 +01:00
|
|
|
} // namespace Luau
|