2021-10-29 21:25:12 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
#include "Luau/Unifier.h"
|
|
|
|
|
|
|
|
#include "Luau/Common.h"
|
|
|
|
#include "Luau/RecursionCounter.h"
|
2021-11-05 02:34:35 +00:00
|
|
|
#include "Luau/Scope.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
#include "Luau/TypePack.h"
|
|
|
|
#include "Luau/TypeUtils.h"
|
2021-11-05 02:34:35 +00:00
|
|
|
#include "Luau/TimeTrace.h"
|
2021-11-05 15:47:21 +00:00
|
|
|
#include "Luau/VisitTypeVar.h"
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
LUAU_FASTINT(LuauTypeInferRecursionLimit);
|
|
|
|
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit);
|
2022-01-07 01:46:53 +00:00
|
|
|
LUAU_FASTFLAG(LuauUseCommittingTxnLog)
|
2021-11-05 02:34:35 +00:00
|
|
|
LUAU_FASTINTVARIABLE(LuauTypeInferIterationLimit, 2000);
|
2021-12-10 22:05:05 +00:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauTableSubtypingVariance2, false);
|
2021-10-29 21:25:12 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauUnionHeuristic, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauTableUnificationEarlyTest, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauOccursCheckOkWithRecursiveFunctions, false)
|
2021-11-19 16:10:07 +00:00
|
|
|
LUAU_FASTFLAG(LuauSingletonTypes)
|
|
|
|
LUAU_FASTFLAG(LuauErrorRecoveryType);
|
2021-12-10 22:05:05 +00:00
|
|
|
LUAU_FASTFLAG(LuauProperTypeLevels);
|
2022-01-14 16:20:09 +00:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauUnifyPackTails, false)
|
2021-12-10 22:05:05 +00:00
|
|
|
LUAU_FASTFLAGVARIABLE(LuauExtendedUnionMismatchError, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauExtendedFunctionMismatchError, false)
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
struct PromoteTypeLevels
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_TxnLog& DEPRECATED_log;
|
2021-12-10 22:05:05 +00:00
|
|
|
TxnLog& log;
|
|
|
|
TypeLevel minLevel;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
explicit PromoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel)
|
|
|
|
: DEPRECATED_log(DEPRECATED_log)
|
|
|
|
, log(log)
|
2021-12-10 22:05:05 +00:00
|
|
|
, minLevel(minLevel)
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
}
|
2021-12-10 22:05:05 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
template<typename TID, typename T>
|
2021-12-10 22:05:05 +00:00
|
|
|
void promote(TID ty, T* t)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(t);
|
|
|
|
if (minLevel.subsumesStrict(t->level))
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.changeLevel(ty, minLevel);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(ty);
|
|
|
|
t->level = minLevel;
|
|
|
|
}
|
2021-12-10 22:05:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename TID>
|
2022-01-07 01:46:53 +00:00
|
|
|
void cycle(TID)
|
|
|
|
{
|
|
|
|
}
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
template<typename TID, typename T>
|
|
|
|
bool operator()(TID, const T&)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const FreeTypeVar&)
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
// Surprise, it's actually a BoundTypeVar that hasn't been committed yet.
|
|
|
|
// Calling getMutable on this will trigger an assertion.
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog && !log.is<FreeTypeVar>(ty))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypeVar>(ty) : getMutable<FreeTypeVar>(ty));
|
2021-12-10 22:05:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const FunctionTypeVar&)
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FunctionTypeVar>(ty) : getMutable<FunctionTypeVar>(ty));
|
2021-12-10 22:05:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
bool operator()(TypeId ty, const TableTypeVar& ttv)
|
2021-12-10 22:05:05 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (ttv.state != TableState::Free && ttv.state != TableState::Generic)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
promote(ty, FFlag::LuauUseCommittingTxnLog ? log.getMutable<TableTypeVar>(ty) : getMutable<TableTypeVar>(ty));
|
2021-12-10 22:05:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypePackId tp, const FreeTypePack&)
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
promote(tp, FFlag::LuauUseCommittingTxnLog ? log.getMutable<FreeTypePack>(tp) : getMutable<FreeTypePack>(tp));
|
2021-12-10 22:05:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypeId ty)
|
2021-12-10 22:05:05 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
|
2021-12-10 22:05:05 +00:00
|
|
|
DenseHashSet<void*> seen{nullptr};
|
|
|
|
visitTypeVarOnce(ty, ptl, seen);
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void promoteTypeLevels(DEPRECATED_TxnLog& DEPRECATED_log, TxnLog& log, TypeLevel minLevel, TypePackId tp)
|
2021-12-10 22:05:05 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
PromoteTypeLevels ptl{DEPRECATED_log, log, minLevel};
|
2021-12-10 22:05:05 +00:00
|
|
|
DenseHashSet<void*> seen{nullptr};
|
|
|
|
visitTypeVarOnce(tp, ptl, seen);
|
|
|
|
}
|
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
struct SkipCacheForType
|
|
|
|
{
|
|
|
|
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType)
|
|
|
|
: skipCacheForType(skipCacheForType)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void cycle(TypeId) {}
|
|
|
|
void cycle(TypePackId) {}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const FreeTypeVar& ftv)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const BoundTypeVar& btv)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const GenericTypeVar& btv)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypeId ty, const TableTypeVar&)
|
|
|
|
{
|
|
|
|
TableTypeVar& ttv = *getMutable<TableTypeVar>(ty);
|
|
|
|
|
|
|
|
if (ttv.boundTo)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ttv.state != TableState::Sealed)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
bool operator()(TypeId ty, const T& t)
|
|
|
|
{
|
|
|
|
const bool* prev = skipCacheForType.find(ty);
|
|
|
|
|
|
|
|
if (prev && *prev)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
bool operator()(TypePackId, const T&)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypePackId tp, const FreeTypePack& ftp)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypePackId tp, const BoundTypePack& ftp)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator()(TypePackId tp, const GenericTypePack& ftp)
|
|
|
|
{
|
|
|
|
result = true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const DenseHashMap<TypeId, bool>& skipCacheForType;
|
|
|
|
bool result = false;
|
|
|
|
};
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
static std::optional<TypeError> hasUnificationTooComplex(const ErrorVec& errors)
|
|
|
|
{
|
|
|
|
auto isUnificationTooComplex = [](const TypeError& te) {
|
|
|
|
return nullptr != get<UnificationTooComplex>(te);
|
|
|
|
};
|
|
|
|
|
|
|
|
auto it = std::find_if(errors.begin(), errors.end(), isUnificationTooComplex);
|
|
|
|
if (it == errors.end())
|
|
|
|
return std::nullopt;
|
|
|
|
else
|
|
|
|
return *it;
|
|
|
|
}
|
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
// Used for tagged union matching heuristic, returns first singleton type field
|
|
|
|
static std::optional<std::pair<Luau::Name, const SingletonTypeVar*>> getTableMatchTag(TypeId type)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(FFlag::LuauExtendedUnionMismatchError);
|
|
|
|
|
|
|
|
type = follow(type);
|
|
|
|
|
|
|
|
if (auto ttv = get<TableTypeVar>(type))
|
|
|
|
{
|
|
|
|
for (auto&& [name, prop] : ttv->props)
|
|
|
|
{
|
|
|
|
if (auto sing = get<SingletonTypeVar>(follow(prop.type)))
|
|
|
|
return {{name, sing}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto mttv = get<MetatableTypeVar>(type))
|
|
|
|
{
|
|
|
|
return getTableMatchTag(mttv->table);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, const Location& location, Variance variance, UnifierSharedState& sharedState,
|
|
|
|
TxnLog* parentLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
: types(types)
|
|
|
|
, mode(mode)
|
|
|
|
, globalScope(std::move(globalScope))
|
2022-01-07 01:46:53 +00:00
|
|
|
, log(parentLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
, location(location)
|
|
|
|
, variance(variance)
|
2021-11-05 15:47:21 +00:00
|
|
|
, sharedState(sharedState)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
LUAU_ASSERT(sharedState.iceHandler);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
Unifier::Unifier(TypeArena* types, Mode mode, ScopePtr globalScope, std::vector<std::pair<TypeId, TypeId>>* sharedSeen, const Location& location,
|
2022-01-07 01:46:53 +00:00
|
|
|
Variance variance, UnifierSharedState& sharedState, TxnLog* parentLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
: types(types)
|
|
|
|
, mode(mode)
|
|
|
|
, globalScope(std::move(globalScope))
|
2022-01-07 01:46:53 +00:00
|
|
|
, DEPRECATED_log(sharedSeen)
|
|
|
|
, log(parentLog, sharedSeen)
|
2021-10-29 21:25:12 +01:00
|
|
|
, location(location)
|
|
|
|
, variance(variance)
|
2021-11-05 15:47:21 +00:00
|
|
|
, sharedState(sharedState)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
LUAU_ASSERT(sharedState.iceHandler);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnify(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.counters.iterationCount = 0;
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(subTy, superTy, isFunctionCall, isIntersection);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
++sharedState.counters.iterationCount;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superTy = log.follow(superTy);
|
|
|
|
subTy = log.follow(subTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
superTy = follow(superTy);
|
|
|
|
subTy = follow(subTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (superTy == subTy)
|
|
|
|
return;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
auto superFree = getMutable<FreeTypeVar>(superTy);
|
|
|
|
auto subFree = getMutable<FreeTypeVar>(subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superFree = log.getMutable<FreeTypeVar>(superTy);
|
|
|
|
subFree = log.getMutable<FreeTypeVar>(subTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superFree && subFree && superFree->level.subsumes(subFree->level))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
occursCheck(subTy, superTy);
|
|
|
|
|
2021-11-19 16:10:07 +00:00
|
|
|
// The occurrence check might have caused superTy no longer to be a free type
|
2022-01-07 01:46:53 +00:00
|
|
|
bool occursFailed = false;
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
occursFailed = bool(log.getMutable<ErrorTypeVar>(subTy));
|
|
|
|
else
|
|
|
|
occursFailed = bool(get<ErrorTypeVar>(subTy));
|
|
|
|
|
|
|
|
if (!occursFailed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.replace(subTy, BoundTypeVar(superTy));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTy);
|
|
|
|
*asMutable(subTy) = BoundTypeVar(superTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superFree && subFree)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!FFlag::LuauErrorRecoveryType && !FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
subFree->level = min(subFree->level, superFree->level);
|
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
occursCheck(superTy, subTy);
|
2021-11-19 16:10:07 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
bool occursFailed = false;
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
occursFailed = bool(log.getMutable<ErrorTypeVar>(superTy));
|
|
|
|
else
|
|
|
|
occursFailed = bool(get<ErrorTypeVar>(superTy));
|
|
|
|
|
|
|
|
if (!FFlag::LuauErrorRecoveryType && !FFlag::LuauUseCommittingTxnLog)
|
2021-11-19 16:10:07 +00:00
|
|
|
{
|
|
|
|
*asMutable(superTy) = BoundTypeVar(subTy);
|
2022-01-07 01:46:53 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!occursFailed)
|
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (superFree->level.subsumes(subFree->level))
|
|
|
|
{
|
|
|
|
log.changeLevel(subTy, superFree->level);
|
|
|
|
}
|
|
|
|
|
|
|
|
log.replace(superTy, BoundTypeVar(subTy));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
*asMutable(superTy) = BoundTypeVar(subTy);
|
|
|
|
subFree->level = min(subFree->level, superFree->level);
|
|
|
|
}
|
2021-11-19 16:10:07 +00:00
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superFree)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
occursCheck(superTy, subTy);
|
2022-01-07 01:46:53 +00:00
|
|
|
bool occursFailed = false;
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
occursFailed = bool(log.getMutable<ErrorTypeVar>(superTy));
|
|
|
|
else
|
|
|
|
occursFailed = bool(get<ErrorTypeVar>(superTy));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeLevel superLevel = superFree->level;
|
2021-12-10 22:05:05 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// Unification can't change the level of a generic.
|
2022-01-07 01:46:53 +00:00
|
|
|
auto subGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable<GenericTypeVar>(subTy) : get<GenericTypeVar>(subTy);
|
|
|
|
if (subGeneric && !subGeneric->level.subsumes(superLevel))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// TODO: a more informative error message? CLI-39912
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Generic subtype escaping scope"}});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-19 16:10:07 +00:00
|
|
|
// The occurrence check might have caused superTy no longer to be a free type
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!occursFailed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
|
|
|
|
log.replace(superTy, BoundTypeVar(subTy));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (FFlag::LuauProperTypeLevels)
|
|
|
|
promoteTypeLevels(DEPRECATED_log, log, superLevel, subTy);
|
|
|
|
else if (auto subLevel = getMutableLevel(subTy))
|
|
|
|
{
|
|
|
|
if (!subLevel->subsumes(superFree->level))
|
|
|
|
*subLevel = superFree->level;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
*asMutable(superTy) = BoundTypeVar(subTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2021-11-19 16:10:07 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subFree)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeLevel subLevel = subFree->level;
|
2021-12-10 22:05:05 +00:00
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
occursCheck(subTy, superTy);
|
2022-01-07 01:46:53 +00:00
|
|
|
bool occursFailed = false;
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
occursFailed = bool(log.getMutable<ErrorTypeVar>(subTy));
|
|
|
|
else
|
|
|
|
occursFailed = bool(get<ErrorTypeVar>(subTy));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// Unification can't change the level of a generic.
|
2022-01-07 01:46:53 +00:00
|
|
|
auto superGeneric = FFlag::LuauUseCommittingTxnLog ? log.getMutable<GenericTypeVar>(superTy) : get<GenericTypeVar>(superTy);
|
|
|
|
if (superGeneric && !superGeneric->level.subsumes(subFree->level))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// TODO: a more informative error message? CLI-39912
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Generic supertype escaping scope"}});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!occursFailed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
|
|
|
|
log.replace(subTy, BoundTypeVar(superTy));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (FFlag::LuauProperTypeLevels)
|
|
|
|
promoteTypeLevels(DEPRECATED_log, log, subLevel, superTy);
|
|
|
|
else if (auto superLevel = getMutableLevel(superTy))
|
2021-12-10 22:05:05 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!superLevel->subsumes(subFree->level))
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
*superLevel = subFree->level;
|
|
|
|
}
|
2021-12-10 22:05:05 +00:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_log(subTy);
|
|
|
|
*asMutable(subTy) = BoundTypeVar(superTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<ErrorTypeVar>(superTy) || get<AnyTypeVar>(superTy))
|
2022-01-07 01:46:53 +00:00
|
|
|
return tryUnifyWithAny(subTy, superTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (get<ErrorTypeVar>(subTy) || get<AnyTypeVar>(subTy))
|
2022-01-07 01:46:53 +00:00
|
|
|
return tryUnifyWithAny(superTy, subTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
bool cacheEnabled = !isFunctionCall && !isIntersection;
|
2021-11-05 15:47:21 +00:00
|
|
|
auto& cache = sharedState.cachedUnify;
|
|
|
|
|
|
|
|
// What if the types are immutable and we proved their relation before
|
|
|
|
if (cacheEnabled && cache.contains({superTy, subTy}) && (variance == Covariant || cache.contains({subTy, superTy})))
|
|
|
|
return;
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// If we have seen this pair of types before, we are currently recursing into cyclic types.
|
|
|
|
// Here, we assume that the types unify. If they do not, we will find out as we roll back
|
|
|
|
// the stack.
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (log.haveSeen(superTy, subTy))
|
|
|
|
return;
|
|
|
|
|
|
|
|
log.pushSeen(superTy, subTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (DEPRECATED_log.haveSeen(superTy, subTy))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_log.pushSeen(superTy, subTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (const UnionTypeVar* uv = FFlag::LuauUseCommittingTxnLog ? log.getMutable<UnionTypeVar>(subTy) : get<UnionTypeVar>(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// A | B <: T if A <: T and B <: T
|
|
|
|
bool failed = false;
|
|
|
|
std::optional<TypeError> unificationTooComplex;
|
2021-11-12 14:27:34 +00:00
|
|
|
std::optional<TypeError> firstFailedOption;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
size_t count = uv->options.size();
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
|
|
for (TypeId type : uv->options)
|
|
|
|
{
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(type, superTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
unificationTooComplex = e;
|
|
|
|
else if (!innerState.errors.empty())
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
|
|
|
// 'nil' option is skipped from extended report because we present the type in a special way - 'T?'
|
2022-01-06 23:26:14 +00:00
|
|
|
if (!firstFailedOption && !isNil(type))
|
2021-11-12 14:27:34 +00:00
|
|
|
firstFailedOption = {innerState.errors.front()};
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
failed = true;
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (i == count - 1)
|
|
|
|
{
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (i != count - 1)
|
|
|
|
{
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
}
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unificationTooComplex)
|
|
|
|
errors.push_back(*unificationTooComplex);
|
|
|
|
else if (failed)
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
if (firstFailedOption)
|
2021-12-10 22:05:05 +00:00
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all union options are compatible.", *firstFailedOption}});
|
2021-11-12 14:27:34 +00:00
|
|
|
else
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (const UnionTypeVar* uv = FFlag::LuauUseCommittingTxnLog ? log.getMutable<UnionTypeVar>(superTy) : get<UnionTypeVar>(superTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// T <: A | B if T <: A or T <: B
|
|
|
|
bool found = false;
|
|
|
|
std::optional<TypeError> unificationTooComplex;
|
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
size_t failedOptionCount = 0;
|
|
|
|
std::optional<TypeError> failedOption;
|
|
|
|
|
|
|
|
bool foundHeuristic = false;
|
2021-10-29 21:25:12 +01:00
|
|
|
size_t startIndex = 0;
|
|
|
|
|
|
|
|
if (FFlag::LuauUnionHeuristic)
|
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
if (const std::string* subName = getName(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < uv->options.size(); ++i)
|
|
|
|
{
|
|
|
|
const std::string* optionName = getName(uv->options[i]);
|
|
|
|
if (optionName && *optionName == *subName)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
foundHeuristic = true;
|
2021-11-05 15:47:21 +00:00
|
|
|
startIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
if (FFlag::LuauExtendedUnionMismatchError)
|
|
|
|
{
|
|
|
|
if (auto subMatchTag = getTableMatchTag(subTy))
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < uv->options.size(); ++i)
|
|
|
|
{
|
|
|
|
auto optionMatchTag = getTableMatchTag(uv->options[i]);
|
|
|
|
if (optionMatchTag && optionMatchTag->first == subMatchTag->first && *optionMatchTag->second == *subMatchTag->second)
|
|
|
|
{
|
|
|
|
foundHeuristic = true;
|
|
|
|
startIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!foundHeuristic && cacheEnabled)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < uv->options.size(); ++i)
|
|
|
|
{
|
|
|
|
TypeId type = uv->options[i];
|
|
|
|
|
|
|
|
if (cache.contains({type, subTy}) && (variance == Covariant || cache.contains({subTy, type})))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
startIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < uv->options.size(); ++i)
|
|
|
|
{
|
|
|
|
TypeId type = uv->options[(i + startIndex) % uv->options.size()];
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subTy, type, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
{
|
|
|
|
found = true;
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
{
|
|
|
|
unificationTooComplex = e;
|
|
|
|
}
|
2021-12-10 22:05:05 +00:00
|
|
|
else if (FFlag::LuauExtendedUnionMismatchError && !isNil(type))
|
|
|
|
{
|
|
|
|
failedOptionCount++;
|
|
|
|
|
|
|
|
if (!failedOption)
|
|
|
|
failedOption = {innerState.errors.front()};
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!FFlag::LuauUseCommittingTxnLog)
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unificationTooComplex)
|
2021-12-10 22:05:05 +00:00
|
|
|
{
|
2021-10-29 21:25:12 +01:00
|
|
|
errors.push_back(*unificationTooComplex);
|
2021-12-10 22:05:05 +00:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
else if (!found)
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
if (FFlag::LuauExtendedUnionMismatchError && (failedOptionCount == 1 || foundHeuristic) && failedOption)
|
|
|
|
errors.push_back(
|
|
|
|
TypeError{location, TypeMismatch{superTy, subTy, "None of the union options are compatible. For example:", *failedOption}});
|
2021-11-12 14:27:34 +00:00
|
|
|
else
|
2022-01-06 23:26:14 +00:00
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "none of the union options are compatible"}});
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (const IntersectionTypeVar* uv =
|
|
|
|
FFlag::LuauUseCommittingTxnLog ? log.getMutable<IntersectionTypeVar>(superTy) : get<IntersectionTypeVar>(superTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
std::optional<TypeError> unificationTooComplex;
|
|
|
|
std::optional<TypeError> firstFailedOption;
|
|
|
|
|
|
|
|
// T <: A & B if A <: T and B <: T
|
|
|
|
for (TypeId type : uv->parts)
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subTy, type, /*isFunctionCall*/ false, /*isIntersection*/ true);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-06 23:26:14 +00:00
|
|
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
unificationTooComplex = e;
|
|
|
|
else if (!innerState.errors.empty())
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
if (!firstFailedOption)
|
|
|
|
firstFailedOption = {innerState.errors.front()};
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-06 23:26:14 +00:00
|
|
|
|
|
|
|
if (unificationTooComplex)
|
|
|
|
errors.push_back(*unificationTooComplex);
|
|
|
|
else if (firstFailedOption)
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Not all intersection parts are compatible.", *firstFailedOption}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (const IntersectionTypeVar* uv =
|
|
|
|
FFlag::LuauUseCommittingTxnLog ? log.getMutable<IntersectionTypeVar>(subTy) : get<IntersectionTypeVar>(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// A & B <: T if T <: A or T <: B
|
|
|
|
bool found = false;
|
|
|
|
std::optional<TypeError> unificationTooComplex;
|
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
size_t startIndex = 0;
|
|
|
|
|
|
|
|
if (cacheEnabled)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
for (size_t i = 0; i < uv->parts.size(); ++i)
|
|
|
|
{
|
|
|
|
TypeId type = uv->parts[i];
|
|
|
|
|
|
|
|
if (cache.contains({superTy, type}) && (variance == Covariant || cache.contains({type, superTy})))
|
|
|
|
{
|
|
|
|
startIndex = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < uv->parts.size(); ++i)
|
|
|
|
{
|
|
|
|
TypeId type = uv->parts[(i + startIndex) % uv->parts.size()];
|
2021-10-29 21:25:12 +01:00
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(type, superTy, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
{
|
|
|
|
found = true;
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
2021-10-29 21:25:12 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
{
|
|
|
|
unificationTooComplex = e;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!FFlag::LuauUseCommittingTxnLog)
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unificationTooComplex)
|
|
|
|
errors.push_back(*unificationTooComplex);
|
|
|
|
else if (!found)
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "none of the intersection parts are compatible"}});
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<PrimitiveTypeVar>(superTy) && log.getMutable<PrimitiveTypeVar>(subTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<PrimitiveTypeVar>(superTy) && get<PrimitiveTypeVar>(subTy)))
|
|
|
|
tryUnifyPrimitives(subTy, superTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (FFlag::LuauSingletonTypes &&
|
|
|
|
((FFlag::LuauUseCommittingTxnLog ? log.getMutable<PrimitiveTypeVar>(superTy) : get<PrimitiveTypeVar>(superTy)) ||
|
|
|
|
(FFlag::LuauUseCommittingTxnLog ? log.getMutable<SingletonTypeVar>(superTy) : get<SingletonTypeVar>(superTy))) &&
|
|
|
|
(FFlag::LuauUseCommittingTxnLog ? log.getMutable<SingletonTypeVar>(subTy) : get<SingletonTypeVar>(subTy)))
|
|
|
|
tryUnifySingletons(subTy, superTy);
|
2021-11-19 16:10:07 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<FunctionTypeVar>(superTy) && log.getMutable<FunctionTypeVar>(subTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<FunctionTypeVar>(superTy) && get<FunctionTypeVar>(subTy)))
|
|
|
|
tryUnifyFunctions(subTy, superTy, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<TableTypeVar>(superTy) && log.getMutable<TableTypeVar>(subTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<TableTypeVar>(superTy) && get<TableTypeVar>(subTy)))
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnifyTables(subTy, superTy, isIntersection);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
if (cacheEnabled && errors.empty())
|
2022-01-07 01:46:53 +00:00
|
|
|
cacheResult(subTy, superTy);
|
2021-11-05 15:47:21 +00:00
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
// tryUnifyWithMetatable assumes its first argument is a MetatableTypeVar. The check is otherwise symmetrical.
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<MetatableTypeVar>(superTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<MetatableTypeVar>(superTy)))
|
|
|
|
tryUnifyWithMetatable(subTy, superTy, /*reversed*/ false);
|
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<MetatableTypeVar>(subTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<MetatableTypeVar>(subTy)))
|
|
|
|
tryUnifyWithMetatable(superTy, subTy, /*reversed*/ true);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<ClassTypeVar>(superTy)) ||
|
|
|
|
(!FFlag::LuauUseCommittingTxnLog && get<ClassTypeVar>(superTy)))
|
|
|
|
tryUnifyWithClass(subTy, superTy, /*reversed*/ false);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// Unification of nonclasses with classes is almost, but not quite symmetrical.
|
|
|
|
// The order in which we perform this test is significant in the case that both types are classes.
|
2022-01-07 01:46:53 +00:00
|
|
|
else if ((FFlag::LuauUseCommittingTxnLog && log.getMutable<ClassTypeVar>(subTy)) || (!FFlag::LuauUseCommittingTxnLog && get<ClassTypeVar>(subTy)))
|
|
|
|
tryUnifyWithClass(subTy, superTy, /*reversed*/ true);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
else
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.popSeen(superTy, subTy);
|
|
|
|
else
|
|
|
|
DEPRECATED_log.popSeen(superTy, subTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::cacheResult(TypeId subTy, TypeId superTy)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
|
|
|
bool* superTyInfo = sharedState.skipCacheForType.find(superTy);
|
|
|
|
|
|
|
|
if (superTyInfo && *superTyInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool* subTyInfo = sharedState.skipCacheForType.find(subTy);
|
|
|
|
|
|
|
|
if (subTyInfo && *subTyInfo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto skipCacheFor = [this](TypeId ty) {
|
|
|
|
SkipCacheForType visitor{sharedState.skipCacheForType};
|
|
|
|
visitTypeVarOnce(ty, visitor, sharedState.seenAny);
|
|
|
|
|
|
|
|
sharedState.skipCacheForType[ty] = visitor.result;
|
|
|
|
|
|
|
|
return visitor.result;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!superTyInfo && skipCacheFor(superTy))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!subTyInfo && skipCacheFor(subTy))
|
|
|
|
return;
|
|
|
|
|
|
|
|
sharedState.cachedUnify.insert({superTy, subTy});
|
|
|
|
|
|
|
|
if (variance == Invariant)
|
|
|
|
sharedState.cachedUnify.insert({subTy, superTy});
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
struct DEPRECATED_WeirdIter
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
TypePackId packId;
|
|
|
|
const TypePack* pack;
|
|
|
|
size_t index;
|
|
|
|
bool growing;
|
|
|
|
TypeLevel level;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_WeirdIter(TypePackId packId)
|
2021-10-29 21:25:12 +01:00
|
|
|
: packId(packId)
|
|
|
|
, pack(get<TypePack>(packId))
|
|
|
|
, index(0)
|
|
|
|
, growing(false)
|
|
|
|
{
|
|
|
|
while (pack && pack->head.empty() && pack->tail)
|
|
|
|
{
|
|
|
|
packId = *pack->tail;
|
|
|
|
pack = get<TypePack>(packId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
DEPRECATED_WeirdIter(const DEPRECATED_WeirdIter&) = default;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
const TypeId& operator*()
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(good());
|
|
|
|
return pack->head[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool good() const
|
|
|
|
{
|
|
|
|
return pack != nullptr && index < pack->head.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool advance()
|
|
|
|
{
|
|
|
|
if (!pack)
|
|
|
|
return good();
|
|
|
|
|
|
|
|
if (index < pack->head.size())
|
|
|
|
++index;
|
|
|
|
|
|
|
|
if (growing || index < pack->head.size())
|
|
|
|
return good();
|
|
|
|
|
|
|
|
if (pack->tail)
|
|
|
|
{
|
|
|
|
packId = follow(*pack->tail);
|
|
|
|
pack = get<TypePack>(packId);
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return good();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool canGrow() const
|
|
|
|
{
|
|
|
|
return nullptr != get<Unifiable::Free>(packId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void grow(TypePackId newTail)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(canGrow());
|
|
|
|
level = get<Unifiable::Free>(packId)->level;
|
|
|
|
*asMutable(packId) = Unifiable::Bound<TypePackId>(newTail);
|
|
|
|
packId = newTail;
|
|
|
|
pack = get<TypePack>(newTail);
|
|
|
|
index = 0;
|
|
|
|
growing = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
struct WeirdIter
|
|
|
|
{
|
|
|
|
TypePackId packId;
|
|
|
|
TxnLog& log;
|
|
|
|
TypePack* pack;
|
|
|
|
size_t index;
|
|
|
|
bool growing;
|
|
|
|
TypeLevel level;
|
|
|
|
|
|
|
|
WeirdIter(TypePackId packId, TxnLog& log)
|
|
|
|
: packId(packId)
|
|
|
|
, log(log)
|
|
|
|
, pack(log.getMutable<TypePack>(packId))
|
|
|
|
, index(0)
|
|
|
|
, growing(false)
|
|
|
|
{
|
|
|
|
while (pack && pack->head.empty() && pack->tail)
|
|
|
|
{
|
|
|
|
packId = *pack->tail;
|
|
|
|
pack = log.getMutable<TypePack>(packId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WeirdIter(const WeirdIter&) = default;
|
|
|
|
|
|
|
|
TypeId& operator*()
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(good());
|
|
|
|
return pack->head[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
bool good() const
|
|
|
|
{
|
|
|
|
return pack != nullptr && index < pack->head.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool advance()
|
|
|
|
{
|
|
|
|
if (!pack)
|
|
|
|
return good();
|
|
|
|
|
|
|
|
if (index < pack->head.size())
|
|
|
|
++index;
|
|
|
|
|
|
|
|
if (growing || index < pack->head.size())
|
|
|
|
return good();
|
|
|
|
|
|
|
|
if (pack->tail)
|
|
|
|
{
|
|
|
|
packId = log.follow(*pack->tail);
|
|
|
|
pack = log.getMutable<TypePack>(packId);
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return good();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool canGrow() const
|
|
|
|
{
|
|
|
|
return nullptr != log.getMutable<Unifiable::Free>(packId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void grow(TypePackId newTail)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(canGrow());
|
|
|
|
LUAU_ASSERT(log.getMutable<TypePack>(newTail));
|
|
|
|
|
|
|
|
level = log.getMutable<Unifiable::Free>(packId)->level;
|
|
|
|
log.replace(packId, Unifiable::Bound<TypePackId>(newTail));
|
|
|
|
packId = newTail;
|
|
|
|
pack = log.getMutable<TypePack>(newTail);
|
|
|
|
index = 0;
|
|
|
|
growing = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pushType(TypeId ty)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(pack);
|
|
|
|
PendingTypePack* pendingPack = log.queue(packId);
|
|
|
|
if (TypePack* pending = getMutable<TypePack>(pendingPack))
|
|
|
|
{
|
|
|
|
pending->head.push_back(ty);
|
|
|
|
// We've potentially just replaced the TypePack* that we need to look
|
|
|
|
// in. We need to replace pack.
|
|
|
|
pack = pending;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!"Pending state for this pack was not a TypePack");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
Unifier s = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
s.tryUnify_(subTy, superTy);
|
|
|
|
|
|
|
|
if (!FFlag::LuauUseCommittingTxnLog)
|
|
|
|
s.DEPRECATED_log.rollback();
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return s.errors;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
ErrorVec Unifier::canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
Unifier s = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
s.tryUnify_(subTy, superTy, isFunctionCall);
|
|
|
|
|
|
|
|
if (!FFlag::LuauUseCommittingTxnLog)
|
|
|
|
s.DEPRECATED_log.rollback();
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
return s.errors;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnify(TypePackId subTp, TypePackId superTp, bool isFunctionCall)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.counters.iterationCount = 0;
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(subTp, superTp, isFunctionCall);
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::pair<std::vector<TypeId>, std::optional<TypePackId>> logAwareFlatten(TypePackId tp, const TxnLog& log)
|
|
|
|
{
|
|
|
|
tp = log.follow(tp);
|
|
|
|
|
|
|
|
std::vector<TypeId> flattened;
|
|
|
|
std::optional<TypePackId> tail = std::nullopt;
|
|
|
|
|
|
|
|
TypePackIterator it(tp, &log);
|
|
|
|
|
|
|
|
for (; it != end(tp); ++it)
|
|
|
|
{
|
|
|
|
flattened.push_back(*it);
|
|
|
|
}
|
|
|
|
|
|
|
|
tail = it.tail();
|
|
|
|
|
|
|
|
return {flattened, tail};
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is quite tricky: we are walking two rope-like structures and unifying corresponding elements.
|
|
|
|
* If one is longer than the other, but the short end is free, we grow it to the required length.
|
|
|
|
*/
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
++sharedState.counters.iterationCount;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
if (FInt::LuauTypeInferIterationLimit > 0 && FInt::LuauTypeInferIterationLimit < sharedState.counters.iterationCount)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, UnificationTooComplex{}});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
superTp = log.follow(superTp);
|
|
|
|
subTp = log.follow(subTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
while (auto tp = log.getMutable<TypePack>(subTp))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (tp->head.empty() && tp->tail)
|
|
|
|
subTp = log.follow(*tp->tail);
|
|
|
|
else
|
|
|
|
break;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
while (auto tp = log.getMutable<TypePack>(superTp))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (tp->head.empty() && tp->tail)
|
|
|
|
superTp = log.follow(*tp->tail);
|
|
|
|
else
|
|
|
|
break;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTp == subTp)
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (log.getMutable<Unifiable::Free>(superTp))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
occursCheck(superTp, subTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!log.getMutable<ErrorTypeVar>(superTp))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
log.replace(superTp, Unifiable::Bound<TypePackId>(subTp));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
}
|
|
|
|
else if (log.getMutable<Unifiable::Free>(subTp))
|
|
|
|
{
|
|
|
|
occursCheck(subTp, superTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!log.getMutable<ErrorTypeVar>(subTp))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
}
|
|
|
|
else if (log.getMutable<Unifiable::Error>(superTp))
|
|
|
|
tryUnifyWithAny(subTp, superTp);
|
|
|
|
else if (log.getMutable<Unifiable::Error>(subTp))
|
|
|
|
tryUnifyWithAny(superTp, subTp);
|
|
|
|
else if (log.getMutable<VariadicTypePack>(superTp))
|
|
|
|
tryUnifyVariadics(subTp, superTp, false);
|
|
|
|
else if (log.getMutable<VariadicTypePack>(subTp))
|
|
|
|
tryUnifyVariadics(superTp, subTp, true);
|
|
|
|
else if (log.getMutable<TypePack>(superTp) && log.getMutable<TypePack>(subTp))
|
|
|
|
{
|
|
|
|
auto superTpv = log.getMutable<TypePack>(superTp);
|
|
|
|
auto subTpv = log.getMutable<TypePack>(subTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
// If the size of two heads does not match, but both packs have free tail
|
|
|
|
// We set the sentinel variable to say so to avoid growing it forever.
|
|
|
|
auto [superTypes, superTail] = logAwareFlatten(superTp, log);
|
|
|
|
auto [subTypes, subTail] = logAwareFlatten(subTp, log);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
bool noInfiniteGrowth =
|
|
|
|
(superTypes.size() != subTypes.size()) && (superTail && get<FreeTypePack>(*superTail)) && (subTail && get<FreeTypePack>(*subTail));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
auto superIter = WeirdIter(superTp, log);
|
|
|
|
auto subIter = WeirdIter(subTp, log);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
auto mkFreshType = [this](TypeLevel level) {
|
|
|
|
return types->freshType(level);
|
|
|
|
};
|
|
|
|
|
|
|
|
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
|
|
|
|
|
|
|
int loopCount = 0;
|
|
|
|
|
|
|
|
do
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit)
|
|
|
|
ice("Detected possibly infinite TypePack growth");
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
++loopCount;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superIter.good() && subIter.growing)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
subIter.pushType(mkFreshType(subIter.level));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subIter.good() && superIter.growing)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
superIter.pushType(mkFreshType(superIter.level));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superIter.good() && subIter.good())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(*subIter, *superIter);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauExtendedFunctionMismatchError && !errors.empty() && !firstPackErrorPos)
|
|
|
|
firstPackErrorPos = loopCount;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
superIter.advance();
|
2022-01-07 01:46:53 +00:00
|
|
|
subIter.advance();
|
|
|
|
continue;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
// If both are at the end, we're done
|
|
|
|
if (!superIter.good() && !subIter.good())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-14 16:20:09 +00:00
|
|
|
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail)
|
|
|
|
{
|
|
|
|
tryUnify_(*subTpv->tail, *superTpv->tail);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
const bool lFreeTail = superTpv->tail && log.getMutable<FreeTypePack>(log.follow(*superTpv->tail)) != nullptr;
|
|
|
|
const bool rFreeTail = subTpv->tail && log.getMutable<FreeTypePack>(log.follow(*subTpv->tail)) != nullptr;
|
2022-01-14 16:20:09 +00:00
|
|
|
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail)
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(*subTpv->tail, *superTpv->tail);
|
|
|
|
else if (lFreeTail)
|
|
|
|
tryUnify_(emptyTp, *superTpv->tail);
|
|
|
|
else if (rFreeTail)
|
|
|
|
tryUnify_(emptyTp, *subTpv->tail);
|
|
|
|
|
|
|
|
break;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
// If both tails are free, bind one to the other and call it a day
|
|
|
|
if (superIter.canGrow() && subIter.canGrow())
|
|
|
|
return tryUnify_(*subIter.pack->tail, *superIter.pack->tail);
|
|
|
|
|
|
|
|
// If just one side is free on its tail, grow it to fit the other side.
|
|
|
|
// FIXME: The tail-most tail of the growing pack should be the same as the tail-most tail of the non-growing pack.
|
|
|
|
if (superIter.canGrow())
|
|
|
|
superIter.grow(types->addTypePack(TypePackVar(TypePack{})));
|
|
|
|
else if (subIter.canGrow())
|
|
|
|
subIter.grow(types->addTypePack(TypePackVar(TypePack{})));
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// A union type including nil marks an optional argument
|
|
|
|
if (superIter.good() && isOptional(*superIter))
|
|
|
|
{
|
|
|
|
superIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (subIter.good() && isOptional(*subIter))
|
|
|
|
{
|
|
|
|
subIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In nonstrict mode, any also marks an optional argument.
|
|
|
|
else if (superIter.good() && isNonstrictMode() && log.getMutable<AnyTypeVar>(log.follow(*superIter)))
|
|
|
|
{
|
|
|
|
superIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (log.getMutable<VariadicTypePack>(superIter.packId))
|
|
|
|
{
|
|
|
|
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (log.getMutable<VariadicTypePack>(subIter.packId))
|
|
|
|
{
|
|
|
|
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isFunctionCall && subIter.good())
|
|
|
|
{
|
|
|
|
// Sometimes it is ok to pass too many arguments
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a bit weird because we don't actually know expected vs actual. We just know
|
|
|
|
// subtype vs supertype. If we are checking the values returned by a function, we swap
|
|
|
|
// these to produce the expected error message.
|
|
|
|
size_t expectedSize = size(superTp);
|
|
|
|
size_t actualSize = size(subTp);
|
|
|
|
if (ctx == CountMismatch::Result)
|
|
|
|
std::swap(expectedSize, actualSize);
|
|
|
|
errors.push_back(TypeError{location, CountMismatch{expectedSize, actualSize, ctx}});
|
|
|
|
|
|
|
|
while (superIter.good())
|
|
|
|
{
|
|
|
|
tryUnify_(*superIter, getSingletonTypes().errorRecoveryType());
|
|
|
|
superIter.advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
while (subIter.good())
|
|
|
|
{
|
|
|
|
tryUnify_(*subIter, getSingletonTypes().errorRecoveryType());
|
|
|
|
subIter.advance();
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (!noInfiniteGrowth);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Failed to unify type packs"}});
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
superTp = follow(superTp);
|
|
|
|
subTp = follow(subTp);
|
|
|
|
|
|
|
|
while (auto tp = get<TypePack>(subTp))
|
|
|
|
{
|
|
|
|
if (tp->head.empty() && tp->tail)
|
|
|
|
subTp = follow(*tp->tail);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (auto tp = get<TypePack>(superTp))
|
|
|
|
{
|
|
|
|
if (tp->head.empty() && tp->tail)
|
|
|
|
superTp = follow(*tp->tail);
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (superTp == subTp)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (get<Unifiable::Free>(superTp))
|
|
|
|
{
|
|
|
|
occursCheck(superTp, subTp);
|
|
|
|
|
|
|
|
if (!get<ErrorTypeVar>(superTp))
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTp);
|
|
|
|
*asMutable(superTp) = Unifiable::Bound<TypePackId>(subTp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (get<Unifiable::Free>(subTp))
|
|
|
|
{
|
|
|
|
occursCheck(subTp, superTp);
|
|
|
|
|
|
|
|
if (!get<ErrorTypeVar>(subTp))
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTp);
|
|
|
|
*asMutable(subTp) = Unifiable::Bound<TypePackId>(superTp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (get<Unifiable::Error>(superTp))
|
|
|
|
tryUnifyWithAny(subTp, superTp);
|
|
|
|
|
|
|
|
else if (get<Unifiable::Error>(subTp))
|
|
|
|
tryUnifyWithAny(superTp, subTp);
|
|
|
|
|
|
|
|
else if (get<VariadicTypePack>(superTp))
|
|
|
|
tryUnifyVariadics(subTp, superTp, false);
|
|
|
|
else if (get<VariadicTypePack>(subTp))
|
|
|
|
tryUnifyVariadics(superTp, subTp, true);
|
|
|
|
|
|
|
|
else if (get<TypePack>(superTp) && get<TypePack>(subTp))
|
|
|
|
{
|
|
|
|
auto superTpv = get<TypePack>(superTp);
|
|
|
|
auto subTpv = get<TypePack>(subTp);
|
|
|
|
|
|
|
|
// If the size of two heads does not match, but both packs have free tail
|
|
|
|
// We set the sentinel variable to say so to avoid growing it forever.
|
|
|
|
auto [superTypes, superTail] = flatten(superTp);
|
|
|
|
auto [subTypes, subTail] = flatten(subTp);
|
|
|
|
|
|
|
|
bool noInfiniteGrowth =
|
|
|
|
(superTypes.size() != subTypes.size()) && (superTail && get<FreeTypePack>(*superTail)) && (subTail && get<FreeTypePack>(*subTail));
|
|
|
|
|
|
|
|
auto superIter = DEPRECATED_WeirdIter{superTp};
|
|
|
|
auto subIter = DEPRECATED_WeirdIter{subTp};
|
|
|
|
|
|
|
|
auto mkFreshType = [this](TypeLevel level) {
|
|
|
|
return types->freshType(level);
|
|
|
|
};
|
|
|
|
|
|
|
|
const TypePackId emptyTp = types->addTypePack(TypePack{{}, std::nullopt});
|
|
|
|
|
|
|
|
int loopCount = 0;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if (FInt::LuauTypeInferTypePackLoopLimit > 0 && loopCount >= FInt::LuauTypeInferTypePackLoopLimit)
|
|
|
|
ice("Detected possibly infinite TypePack growth");
|
|
|
|
|
|
|
|
++loopCount;
|
|
|
|
|
|
|
|
if (superIter.good() && subIter.growing)
|
|
|
|
asMutable(subIter.pack)->head.push_back(mkFreshType(subIter.level));
|
|
|
|
|
|
|
|
if (subIter.good() && superIter.growing)
|
|
|
|
asMutable(superIter.pack)->head.push_back(mkFreshType(superIter.level));
|
|
|
|
|
|
|
|
if (superIter.good() && subIter.good())
|
|
|
|
{
|
|
|
|
tryUnify_(*subIter, *superIter);
|
|
|
|
|
|
|
|
if (FFlag::LuauExtendedFunctionMismatchError && !errors.empty() && !firstPackErrorPos)
|
|
|
|
firstPackErrorPos = loopCount;
|
|
|
|
|
|
|
|
superIter.advance();
|
|
|
|
subIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If both are at the end, we're done
|
|
|
|
if (!superIter.good() && !subIter.good())
|
|
|
|
{
|
2022-01-14 16:20:09 +00:00
|
|
|
if (FFlag::LuauUnifyPackTails && subTpv->tail && superTpv->tail)
|
|
|
|
{
|
|
|
|
tryUnify_(*subTpv->tail, *superTpv->tail);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
const bool lFreeTail = superTpv->tail && get<FreeTypePack>(follow(*superTpv->tail)) != nullptr;
|
|
|
|
const bool rFreeTail = subTpv->tail && get<FreeTypePack>(follow(*subTpv->tail)) != nullptr;
|
2022-01-14 16:20:09 +00:00
|
|
|
if (!FFlag::LuauUnifyPackTails && lFreeTail && rFreeTail)
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(*subTpv->tail, *superTpv->tail);
|
|
|
|
else if (lFreeTail)
|
|
|
|
tryUnify_(emptyTp, *superTpv->tail);
|
|
|
|
else if (rFreeTail)
|
|
|
|
tryUnify_(emptyTp, *subTpv->tail);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If both tails are free, bind one to the other and call it a day
|
|
|
|
if (superIter.canGrow() && subIter.canGrow())
|
|
|
|
return tryUnify_(*subIter.pack->tail, *superIter.pack->tail);
|
|
|
|
|
|
|
|
// If just one side is free on its tail, grow it to fit the other side.
|
|
|
|
// FIXME: The tail-most tail of the growing pack should be the same as the tail-most tail of the non-growing pack.
|
|
|
|
if (superIter.canGrow())
|
|
|
|
superIter.grow(types->addTypePack(TypePackVar(TypePack{})));
|
|
|
|
|
|
|
|
else if (subIter.canGrow())
|
|
|
|
subIter.grow(types->addTypePack(TypePackVar(TypePack{})));
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// A union type including nil marks an optional argument
|
|
|
|
if (superIter.good() && isOptional(*superIter))
|
|
|
|
{
|
|
|
|
superIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (subIter.good() && isOptional(*subIter))
|
|
|
|
{
|
|
|
|
subIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In nonstrict mode, any also marks an optional argument.
|
|
|
|
else if (superIter.good() && isNonstrictMode() && get<AnyTypeVar>(follow(*superIter)))
|
|
|
|
{
|
|
|
|
superIter.advance();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<VariadicTypePack>(superIter.packId))
|
|
|
|
{
|
|
|
|
tryUnifyVariadics(subIter.packId, superIter.packId, false, int(subIter.index));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<VariadicTypePack>(subIter.packId))
|
|
|
|
{
|
|
|
|
tryUnifyVariadics(superIter.packId, subIter.packId, true, int(superIter.index));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isFunctionCall && subIter.good())
|
|
|
|
{
|
|
|
|
// Sometimes it is ok to pass too many arguments
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a bit weird because we don't actually know expected vs actual. We just know
|
|
|
|
// subtype vs supertype. If we are checking the values returned by a function, we swap
|
|
|
|
// these to produce the expected error message.
|
|
|
|
size_t expectedSize = size(superTp);
|
|
|
|
size_t actualSize = size(subTp);
|
|
|
|
if (ctx == CountMismatch::Result)
|
|
|
|
std::swap(expectedSize, actualSize);
|
|
|
|
errors.push_back(TypeError{location, CountMismatch{expectedSize, actualSize, ctx}});
|
|
|
|
|
|
|
|
while (superIter.good())
|
|
|
|
{
|
|
|
|
tryUnify_(*superIter, getSingletonTypes().errorRecoveryType());
|
|
|
|
superIter.advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
while (subIter.good())
|
|
|
|
{
|
|
|
|
tryUnify_(*subIter, getSingletonTypes().errorRecoveryType());
|
|
|
|
subIter.advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
} while (!noInfiniteGrowth);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Failed to unify type packs"}});
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyPrimitives(TypeId subTy, TypeId superTy)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const PrimitiveTypeVar* superPrim = get<PrimitiveTypeVar>(superTy);
|
|
|
|
const PrimitiveTypeVar* subPrim = get<PrimitiveTypeVar>(subTy);
|
|
|
|
if (!superPrim || !subPrim)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non primitive types to unifyPrimitives");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superPrim->type != subPrim->type)
|
2021-10-29 21:25:12 +01:00
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifySingletons(TypeId subTy, TypeId superTy)
|
2021-11-19 16:10:07 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const PrimitiveTypeVar* superPrim = get<PrimitiveTypeVar>(superTy);
|
|
|
|
const SingletonTypeVar* superSingleton = get<SingletonTypeVar>(superTy);
|
|
|
|
const SingletonTypeVar* subSingleton = get<SingletonTypeVar>(subTy);
|
2021-11-19 16:10:07 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if ((!superPrim && !superSingleton) || !subSingleton)
|
2021-11-19 16:10:07 +00:00
|
|
|
ice("passed non singleton/primitive types to unifySingletons");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superSingleton && *superSingleton == *subSingleton)
|
2021-11-19 16:10:07 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superPrim && superPrim->type == PrimitiveTypeVar::Boolean && get<BoolSingleton>(subSingleton) && variance == Covariant)
|
2021-11-19 16:10:07 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superPrim && superPrim->type == PrimitiveTypeVar::String && get<StringSingleton>(subSingleton) && variance == Covariant)
|
2021-11-19 16:10:07 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyFunctions(TypeId subTy, TypeId superTy, bool isFunctionCall)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
FunctionTypeVar* superFunction = getMutable<FunctionTypeVar>(superTy);
|
|
|
|
FunctionTypeVar* subFunction = getMutable<FunctionTypeVar>(subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superFunction = log.getMutable<FunctionTypeVar>(superTy);
|
|
|
|
subFunction = log.getMutable<FunctionTypeVar>(subTy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!superFunction || !subFunction)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non-function types to unifyFunction");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
size_t numGenerics = superFunction->generics.size();
|
|
|
|
if (numGenerics != subFunction->generics.size())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
numGenerics = std::min(superFunction->generics.size(), subFunction->generics.size());
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
if (FFlag::LuauExtendedFunctionMismatchError)
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "different number of generic type parameters"}});
|
|
|
|
else
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
size_t numGenericPacks = superFunction->genericPacks.size();
|
|
|
|
if (numGenericPacks != subFunction->genericPacks.size())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
numGenericPacks = std::min(superFunction->genericPacks.size(), subFunction->genericPacks.size());
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
if (FFlag::LuauExtendedFunctionMismatchError)
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "different number of generic type pack parameters"}});
|
|
|
|
else
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2021-11-12 14:27:34 +00:00
|
|
|
for (size_t i = 0; i < numGenerics; i++)
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.pushSeen(superFunction->generics[i], subFunction->generics[i]);
|
|
|
|
else
|
|
|
|
DEPRECATED_log.pushSeen(superFunction->generics[i], subFunction->generics[i]);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
CountMismatch::Context context = ctx;
|
|
|
|
|
|
|
|
if (!isFunctionCall)
|
|
|
|
{
|
|
|
|
Unifier innerState = makeChildUnifier();
|
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
if (FFlag::LuauExtendedFunctionMismatchError)
|
|
|
|
{
|
|
|
|
innerState.ctx = CountMismatch::Arg;
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
bool reported = !innerState.errors.empty();
|
|
|
|
|
|
|
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
errors.push_back(*e);
|
|
|
|
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
|
|
|
errors.push_back(
|
|
|
|
TypeError{location, TypeMismatch{superTy, subTy, format("Argument #%d type is not compatible.", *innerState.firstPackErrorPos),
|
|
|
|
innerState.errors.front()}});
|
|
|
|
else if (!innerState.errors.empty())
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
|
|
|
|
|
|
|
innerState.ctx = CountMismatch::Result;
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subFunction->retType, superFunction->retType);
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
if (!reported)
|
|
|
|
{
|
|
|
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
errors.push_back(*e);
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (!innerState.errors.empty() && size(superFunction->retType) == 1 && finite(superFunction->retType))
|
2021-12-10 22:05:05 +00:00
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "Return type is not compatible.", innerState.errors.front()}});
|
|
|
|
else if (!innerState.errors.empty() && innerState.firstPackErrorPos)
|
|
|
|
errors.push_back(
|
|
|
|
TypeError{location, TypeMismatch{superTy, subTy, format("Return #%d type is not compatible.", *innerState.firstPackErrorPos),
|
|
|
|
innerState.errors.front()}});
|
|
|
|
else if (!innerState.errors.empty())
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy, "", innerState.errors.front()}});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctx = CountMismatch::Arg;
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
ctx = CountMismatch::Result;
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subFunction->retType, superFunction->retType);
|
2021-12-10 22:05:05 +00:00
|
|
|
|
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ctx = CountMismatch::Arg;
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(superFunction->argTypes, subFunction->argTypes, isFunctionCall);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
ctx = CountMismatch::Result;
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(subFunction->retType, superFunction->retType);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
|
|
|
{
|
|
|
|
PendingType* newSubTy = log.queue(subTy);
|
|
|
|
FunctionTypeVar* newSubFtv = getMutable<FunctionTypeVar>(newSubTy);
|
|
|
|
LUAU_ASSERT(newSubFtv);
|
|
|
|
newSubFtv->definition = superFunction->definition;
|
|
|
|
}
|
|
|
|
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
|
|
|
{
|
|
|
|
PendingType* newSuperTy = log.queue(superTy);
|
|
|
|
FunctionTypeVar* newSuperFtv = getMutable<FunctionTypeVar>(newSuperTy);
|
|
|
|
LUAU_ASSERT(newSuperFtv);
|
|
|
|
newSuperFtv->definition = subFunction->definition;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superFunction->definition && !subFunction->definition && !subTy->persistent)
|
|
|
|
{
|
|
|
|
subFunction->definition = superFunction->definition;
|
|
|
|
}
|
|
|
|
else if (!superFunction->definition && subFunction->definition && !superTy->persistent)
|
|
|
|
{
|
|
|
|
superFunction->definition = subFunction->definition;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx = context;
|
|
|
|
|
2021-11-12 14:27:34 +00:00
|
|
|
for (int i = int(numGenerics) - 1; 0 <= i; i--)
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.popSeen(superFunction->generics[i], subFunction->generics[i]);
|
|
|
|
else
|
|
|
|
DEPRECATED_log.popSeen(superFunction->generics[i], subFunction->generics[i]);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
struct Resetter
|
|
|
|
{
|
|
|
|
explicit Resetter(Variance* variance)
|
|
|
|
: oldValue(*variance)
|
|
|
|
, variance(variance)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Variance oldValue;
|
|
|
|
Variance* variance;
|
|
|
|
|
|
|
|
~Resetter()
|
|
|
|
{
|
|
|
|
*variance = oldValue;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
if (!FFlag::LuauTableSubtypingVariance2)
|
2022-01-07 01:46:53 +00:00
|
|
|
return DEPRECATED_tryUnifyTables(subTy, superTy, isIntersection);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
TableTypeVar* superTable = getMutable<TableTypeVar>(superTy);
|
|
|
|
TableTypeVar* subTable = getMutable<TableTypeVar>(subTy);
|
|
|
|
if (!superTable || !subTable)
|
2021-11-05 02:34:35 +00:00
|
|
|
ice("passed non-table types to unifyTables");
|
|
|
|
|
|
|
|
std::vector<std::string> missingProperties;
|
|
|
|
std::vector<std::string> extraProperties;
|
|
|
|
|
2021-11-05 15:47:21 +00:00
|
|
|
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer && subTable->state != TableState::Free)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [propName, superProp] : superTable->props)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
auto subIter = subTable->props.find(propName);
|
|
|
|
if (subIter == subTable->props.end() && !isOptional(superProp.type) && !get<AnyTypeVar>(follow(superProp.type)))
|
2021-11-05 15:47:21 +00:00
|
|
|
missingProperties.push_back(propName);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!missingProperties.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(missingProperties)}});
|
2021-11-05 15:47:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// And vice versa if we're invariant
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauTableUnificationEarlyTest && variance == Invariant && !superTable->indexer && superTable->state != TableState::Unsealed &&
|
|
|
|
superTable->state != TableState::Free)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [propName, subProp] : subTable->props)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
auto superIter = superTable->props.find(propName);
|
|
|
|
if (superIter == superTable->props.end() && !isOptional(subProp.type) && !get<AnyTypeVar>(follow(subProp.type)))
|
2021-11-05 15:47:21 +00:00
|
|
|
extraProperties.push_back(propName);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!extraProperties.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(extraProperties), MissingProperties::Extra}});
|
2021-11-05 15:47:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-11-19 16:10:07 +00:00
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
// Width subtyping: any property in the supertype must be in the subtype,
|
|
|
|
// and the types must agree.
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [name, prop] : superTable->props)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const auto& r = subTable->props.find(name);
|
|
|
|
if (r != subTable->props.end())
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// TODO: read-only properties don't need invariance
|
|
|
|
Resetter resetter{&variance};
|
|
|
|
variance = Invariant;
|
|
|
|
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(r->second.type, prop.type);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
else
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subTable->indexer && isString(subTable->indexer->indexType))
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// TODO: read-only indexers don't need invariance
|
|
|
|
// TODO: really we should only allow this if prop.type is optional.
|
|
|
|
Resetter resetter{&variance};
|
|
|
|
variance = Invariant;
|
|
|
|
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subTable->indexer->indexResultType, prop.type);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
else
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
else if (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type)))
|
2021-11-05 15:47:21 +00:00
|
|
|
// TODO: this case is unsound, but without it our test suite fails. CLI-46031
|
|
|
|
// TODO: should isOptional(anyType) be true?
|
|
|
|
{
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
PendingType* pendingSub = log.queue(subTy);
|
|
|
|
TableTypeVar* ttv = getMutable<TableTypeVar>(pendingSub);
|
|
|
|
LUAU_ASSERT(ttv);
|
|
|
|
ttv->props[name] = prop;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTy);
|
|
|
|
subTable->props[name] = prop;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
missingProperties.push_back(name);
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [name, prop] : subTable->props)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->props.count(name))
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// If both lt and rt contain the property, then
|
|
|
|
// we're done since we already unified them above
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->indexer && isString(superTable->indexer->indexType))
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// TODO: read-only indexers don't need invariance
|
|
|
|
// TODO: really we should only allow this if prop.type is optional.
|
|
|
|
Resetter resetter{&variance};
|
|
|
|
variance = Invariant;
|
|
|
|
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(superTable->indexer->indexResultType, prop.type);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, name, superTy, subTy);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
else
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state == TableState::Unsealed)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// TODO: this case is unsound when variance is Invariant, but without it lua-apps fails to typecheck.
|
|
|
|
// TODO: file a JIRA
|
|
|
|
// TODO: hopefully readonly/writeonly properties will fix this.
|
|
|
|
Property clone = prop;
|
|
|
|
clone.type = deeplyOptional(clone.type);
|
2022-01-07 01:46:53 +00:00
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
PendingType* pendingSuper = log.queue(superTy);
|
|
|
|
TableTypeVar* pendingSuperTtv = getMutable<TableTypeVar>(pendingSuper);
|
|
|
|
pendingSuperTtv->props[name] = clone;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
superTable->props[name] = clone;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
else if (variance == Covariant)
|
2021-11-05 15:47:21 +00:00
|
|
|
{
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
else if (isOptional(prop.type) || get<AnyTypeVar>(follow(prop.type)))
|
2021-11-05 15:47:21 +00:00
|
|
|
// TODO: this case is unsound, but without it our test suite fails. CLI-46031
|
|
|
|
// TODO: should isOptional(anyType) be true?
|
|
|
|
{
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
PendingType* pendingSuper = log.queue(superTy);
|
|
|
|
TableTypeVar* pendingSuperTtv = getMutable<TableTypeVar>(pendingSuper);
|
|
|
|
pendingSuperTtv->props[name] = prop;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
superTable->props[name] = prop;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
extraProperties.push_back(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unify indexers
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer && subTable->indexer)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// TODO: read-only indexers don't need invariance
|
|
|
|
Resetter resetter{&variance};
|
|
|
|
variance = Invariant;
|
|
|
|
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
{
|
|
|
|
if (innerState.errors.empty())
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
else
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->indexer)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subTable->state == TableState::Unsealed || subTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// passing/assigning a table without an indexer to something that has one
|
|
|
|
// e.g. table.insert(t, 1) where t is a non-sealed table and doesn't have an indexer.
|
|
|
|
// TODO: we only need to do this if the supertype's indexer is read/write
|
|
|
|
// since that can add indexed elements.
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.changeIndexer(subTy, superTable->indexer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTy);
|
|
|
|
subTable->indexer = superTable->indexer;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subTable->indexer && variance == Invariant)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
|
|
|
// Symmetric if we are invariant
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->state == TableState::Unsealed || superTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.changeIndexer(superTy, subTable->indexer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTy);
|
|
|
|
superTable->indexer = subTable->indexer;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!missingProperties.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(missingProperties)}});
|
2021-11-05 02:34:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!extraProperties.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(extraProperties), MissingProperties::Extra}});
|
2021-11-05 02:34:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TypeVars are commonly cyclic, so it is entirely possible
|
|
|
|
* for unifying a property of a table to change the table itself!
|
|
|
|
* We need to check for this and start over if we notice this occurring.
|
|
|
|
*
|
|
|
|
* I believe this is guaranteed to terminate eventually because this will
|
|
|
|
* only happen when a free table is bound to another table.
|
|
|
|
*/
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->boundTo || subTable->boundTo)
|
|
|
|
return tryUnify_(subTy, superTy);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(superTy, subTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTable);
|
|
|
|
superTable->boundTo = subTy;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subTable->state == TableState::Free)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(subTy, superTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTy);
|
|
|
|
subTable->boundTo = superTy;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId Unifier::deeplyOptional(TypeId ty, std::unordered_map<TypeId, TypeId> seen)
|
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
if (get<AnyTypeVar>(ty))
|
|
|
|
return ty;
|
|
|
|
else if (isOptional(ty))
|
|
|
|
return ty;
|
|
|
|
else if (const TableTypeVar* ttv = get<TableTypeVar>(ty))
|
|
|
|
{
|
|
|
|
TypeId& result = seen[ty];
|
|
|
|
if (result)
|
|
|
|
return result;
|
|
|
|
result = types->addType(*ttv);
|
|
|
|
TableTypeVar* resultTtv = getMutable<TableTypeVar>(result);
|
|
|
|
for (auto& [name, prop] : resultTtv->props)
|
|
|
|
prop.type = deeplyOptional(prop.type, seen);
|
2021-12-10 22:05:05 +00:00
|
|
|
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, result}});
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
else
|
2021-12-10 22:05:05 +00:00
|
|
|
return types->addType(UnionTypeVar{{getSingletonTypes().nilType, ty}});
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::DEPRECATED_tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2021-12-10 22:05:05 +00:00
|
|
|
LUAU_ASSERT(!FFlag::LuauTableSubtypingVariance2);
|
2021-11-05 02:34:35 +00:00
|
|
|
Resetter resetter{&variance};
|
2021-10-29 21:25:12 +01:00
|
|
|
variance = Invariant;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
TableTypeVar* superTable = getMutable<TableTypeVar>(superTy);
|
|
|
|
TableTypeVar* subTable = getMutable<TableTypeVar>(subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superTable = log.getMutable<TableTypeVar>(superTy);
|
|
|
|
subTable = log.getMutable<TableTypeVar>(subTy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!superTable || !subTable)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non-table types to unifyTables");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->state == TableState::Sealed && subTable->state == TableState::Sealed)
|
|
|
|
return tryUnifySealedTables(subTy, superTy, isIntersection);
|
|
|
|
else if ((superTable->state == TableState::Sealed && subTable->state == TableState::Unsealed) ||
|
|
|
|
(superTable->state == TableState::Unsealed && subTable->state == TableState::Sealed))
|
|
|
|
return tryUnifySealedTables(subTy, superTy, isIntersection);
|
|
|
|
else if ((superTable->state == TableState::Sealed && subTable->state == TableState::Generic) ||
|
|
|
|
(superTable->state == TableState::Generic && subTable->state == TableState::Sealed))
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
else if ((superTable->state == TableState::Free) != (subTable->state == TableState::Free)) // one table is free and the other is not
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeId freeTypeId = subTable->state == TableState::Free ? subTy : superTy;
|
|
|
|
TypeId otherTypeId = subTable->state == TableState::Free ? superTy : subTy;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
return tryUnifyFreeTable(otherTypeId, freeTypeId);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state == TableState::Free && subTable->state == TableState::Free)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnifyFreeTable(subTy, superTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// avoid creating a cycle when the types are already pointing at each other
|
2022-01-07 01:46:53 +00:00
|
|
|
if (follow(superTy) != follow(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(superTy, subTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(superTable);
|
|
|
|
superTable->boundTo = subTy;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state != TableState::Sealed && subTable->state != TableState::Sealed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// All free tables are checked in one of the branches above
|
2022-01-07 01:46:53 +00:00
|
|
|
LUAU_ASSERT(superTable->state != TableState::Free);
|
|
|
|
LUAU_ASSERT(subTable->state != TableState::Free);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
// Tables must have exactly the same props and their types must all unify
|
|
|
|
// I honestly have no idea if this is remotely close to reasonable.
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [name, prop] : superTable->props)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const auto& r = subTable->props.find(name);
|
|
|
|
if (r == subTable->props.end())
|
|
|
|
errors.push_back(TypeError{location, UnknownProperty{subTy, name}});
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(r->second.type, prop.type);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer && subTable->indexer)
|
|
|
|
tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
|
|
|
else if (superTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// passing/assigning a table without an indexer to something that has one
|
|
|
|
// e.g. table.insert(t, 1) where t is a non-sealed table and doesn't have an indexer.
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subTable->state == TableState::Unsealed)
|
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.changeIndexer(subTy, superTable->indexer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
subTable->indexer = superTable->indexer;
|
|
|
|
}
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, CannotExtendTable{subTy, CannotExtendTable::Indexer}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state == TableState::Sealed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// lt is sealed and so it must be possible for rt to have precisely the same shape
|
|
|
|
// Verify that this is the case, then bind rt to lt.
|
|
|
|
ice("unsealed tables are not working yet", location);
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (subTable->state == TableState::Sealed)
|
|
|
|
return tryUnifyTables(superTy, subTy, isIntersection);
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
|
|
|
ice("tryUnifyTables");
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyFreeTable(TypeId subTy, TypeId superTy)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TableTypeVar* freeTable = getMutable<TableTypeVar>(superTy);
|
|
|
|
TableTypeVar* subTable = getMutable<TableTypeVar>(subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
freeTable = log.getMutable<TableTypeVar>(superTy);
|
|
|
|
subTable = log.getMutable<TableTypeVar>(subTy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!freeTable || !subTable)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non-table types to tryUnifyFreeTable");
|
|
|
|
|
|
|
|
// Any properties in freeTable must unify with those in otherTable.
|
|
|
|
// Then bind freeTable to otherTable.
|
|
|
|
for (const auto& [freeName, freeProp] : freeTable->props)
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (auto subProp = findTablePropertyRespectingMeta(subTy, freeName))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(freeProp.type, *subProp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* TypeVars are commonly cyclic, so it is entirely possible
|
|
|
|
* for unifying a property of a table to change the table itself!
|
|
|
|
* We need to check for this and start over if we notice this occurring.
|
|
|
|
*
|
|
|
|
* I believe this is guaranteed to terminate eventually because this will
|
|
|
|
* only happen when a free table is bound to another table.
|
|
|
|
*/
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (!log.getMutable<TableTypeVar>(superTy) || !log.getMutable<TableTypeVar>(subTy))
|
|
|
|
return tryUnify_(subTy, superTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (TableTypeVar* pendingFreeTtv = log.getMutable<TableTypeVar>(superTy); pendingFreeTtv && pendingFreeTtv->boundTo)
|
|
|
|
return tryUnify_(subTy, superTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!get<TableTypeVar>(superTy) || !get<TableTypeVar>(subTy))
|
|
|
|
return tryUnify_(subTy, superTy);
|
|
|
|
|
|
|
|
if (freeTable->boundTo)
|
|
|
|
return tryUnify_(subTy, superTy);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If the other table is also free, then we are learning that it has more
|
|
|
|
// properties than we previously thought. Else, it is an error.
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subTable->state == TableState::Free)
|
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
PendingType* pendingSub = log.queue(subTy);
|
|
|
|
TableTypeVar* pendingSubTtv = getMutable<TableTypeVar>(pendingSub);
|
|
|
|
LUAU_ASSERT(pendingSubTtv);
|
|
|
|
pendingSubTtv->props.insert({freeName, freeProp});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
subTable->props.insert({freeName, freeProp});
|
|
|
|
}
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
else
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, UnknownProperty{subTy, freeName}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (freeTable->indexer && subTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnifyIndexer(*subTable->indexer, *freeTable->indexer);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
}
|
|
|
|
else if (subTable->state == TableState::Free && freeTable->indexer)
|
|
|
|
{
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.changeIndexer(superTy, subTable->indexer);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
freeTable->indexer = subTable->indexer;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!freeTable->boundTo && subTable->state != TableState::Free)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(superTy, subTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(freeTable);
|
|
|
|
freeTable->boundTo = subTy;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifySealedTables(TypeId subTy, TypeId superTy, bool isIntersection)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TableTypeVar* superTable = getMutable<TableTypeVar>(superTy);
|
|
|
|
TableTypeVar* subTable = getMutable<TableTypeVar>(subTy);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superTable = log.getMutable<TableTypeVar>(superTy);
|
|
|
|
subTable = log.getMutable<TableTypeVar>(subTy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!superTable || !subTable)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non-table types to unifySealedTables");
|
|
|
|
|
|
|
|
Unifier innerState = makeChildUnifier();
|
|
|
|
|
|
|
|
std::vector<std::string> missingPropertiesInSuper;
|
2022-01-07 01:46:53 +00:00
|
|
|
bool isUnnamedTable = subTable->name == std::nullopt && subTable->syntheticName == std::nullopt;
|
2021-10-29 21:25:12 +01:00
|
|
|
bool errorReported = false;
|
|
|
|
|
|
|
|
// Optimization: First test that the property sets are compatible without doing any recursive unification
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauTableUnificationEarlyTest && !subTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [propName, superProp] : superTable->props)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
auto subIter = subTable->props.find(propName);
|
|
|
|
if (subIter == subTable->props.end() && !isOptional(superProp.type))
|
2021-10-29 21:25:12 +01:00
|
|
|
missingPropertiesInSuper.push_back(propName);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!missingPropertiesInSuper.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(missingPropertiesInSuper)}});
|
2021-10-29 21:25:12 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tables must have exactly the same props and their types must all unify
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& it : superTable->props)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const auto& r = subTable->props.find(it.first);
|
|
|
|
if (r == subTable->props.end())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-11-12 14:27:34 +00:00
|
|
|
if (isOptional(it.second.type))
|
|
|
|
continue;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
missingPropertiesInSuper.push_back(it.first);
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (isUnnamedTable && r->second.location)
|
|
|
|
{
|
|
|
|
size_t oldErrorSize = innerState.errors.size();
|
|
|
|
Location old = innerState.location;
|
|
|
|
innerState.location = *r->second.location;
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(r->second.type, it.second.type);
|
2021-10-29 21:25:12 +01:00
|
|
|
innerState.location = old;
|
|
|
|
|
|
|
|
if (oldErrorSize != innerState.errors.size() && !errorReported)
|
|
|
|
{
|
|
|
|
errorReported = true;
|
|
|
|
errors.push_back(innerState.errors.back());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(r->second.type, it.second.type);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer || subTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer && subTable->indexer)
|
|
|
|
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
|
|
|
else if (subTable->state == TableState::Unsealed)
|
|
|
|
{
|
|
|
|
if (superTable->indexer && !subTable->indexer)
|
|
|
|
{
|
|
|
|
log.changeIndexer(subTy, superTable->indexer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (superTable->state == TableState::Unsealed)
|
|
|
|
{
|
|
|
|
if (subTable->indexer && !superTable->indexer)
|
|
|
|
{
|
|
|
|
log.changeIndexer(superTy, subTable->indexer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (superTable->indexer)
|
|
|
|
{
|
|
|
|
innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType);
|
|
|
|
for (const auto& [name, type] : subTable->props)
|
|
|
|
{
|
|
|
|
const auto& it = superTable->props.find(name);
|
|
|
|
if (it == superTable->props.end())
|
|
|
|
innerState.tryUnify_(type.type, superTable->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
innerState.errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer && subTable->indexer)
|
|
|
|
innerState.tryUnifyIndexer(*subTable->indexer, *superTable->indexer);
|
|
|
|
else if (subTable->state == TableState::Unsealed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (superTable->indexer && !subTable->indexer)
|
|
|
|
subTable->indexer = superTable->indexer;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (superTable->state == TableState::Unsealed)
|
|
|
|
{
|
|
|
|
if (subTable->indexer && !superTable->indexer)
|
|
|
|
superTable->indexer = subTable->indexer;
|
|
|
|
}
|
|
|
|
else if (superTable->indexer)
|
|
|
|
{
|
|
|
|
innerState.tryUnify_(getSingletonTypes().stringType, superTable->indexer->indexType);
|
|
|
|
// We already try to unify properties in both tables.
|
|
|
|
// Skip those and just look for the ones remaining and see if they fit into the indexer.
|
|
|
|
for (const auto& [name, type] : subTable->props)
|
|
|
|
{
|
|
|
|
const auto& it = superTable->props.find(name);
|
|
|
|
if (it == superTable->props.end())
|
|
|
|
innerState.tryUnify_(type.type, superTable->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
innerState.errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
if (!errorReported)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
if (errorReported)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!missingPropertiesInSuper.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(missingPropertiesInSuper)}});
|
2021-10-29 21:25:12 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
// If the superTy is an immediate part of an intersection type, do not do extra-property check.
|
2021-10-29 21:25:12 +01:00
|
|
|
// Otherwise, we would falsely generate an extra-property-error for 's' in this code:
|
|
|
|
// local a: {n: number} & {s: string} = {n=1, s=""}
|
2021-11-04 14:50:46 +00:00
|
|
|
// When checking against the table '{n: number}'.
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!isIntersection && superTable->state != TableState::Unsealed && !superTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
// Check for extra properties in the subTy
|
|
|
|
std::vector<std::string> extraPropertiesInSub;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [subKey, subProp] : subTable->props)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const auto& superIt = superTable->props.find(subKey);
|
|
|
|
if (superIt == superTable->props.end())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (isOptional(subProp.type))
|
2021-11-12 14:27:34 +00:00
|
|
|
continue;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
extraPropertiesInSub.push_back(subKey);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!extraPropertiesInSub.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, MissingProperties{superTy, subTy, std::move(extraPropertiesInSub), MissingProperties::Extra}});
|
2021-10-29 21:25:12 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, superTy, subTy);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const MetatableTypeVar* superMetatable = get<MetatableTypeVar>(superTy);
|
|
|
|
if (!superMetatable)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("tryUnifyMetatable invoked with non-metatable TypeVar");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeError mismatchError = TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy}};
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (const MetatableTypeVar* subMetatable =
|
|
|
|
FFlag::LuauUseCommittingTxnLog ? log.getMutable<MetatableTypeVar>(subTy) : get<MetatableTypeVar>(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(subMetatable->table, superMetatable->table);
|
|
|
|
innerState.tryUnify_(subMetatable->metatable, superMetatable->metatable);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-06 23:26:14 +00:00
|
|
|
if (auto e = hasUnificationTooComplex(innerState.errors))
|
|
|
|
errors.push_back(*e);
|
|
|
|
else if (!innerState.errors.empty())
|
|
|
|
errors.push_back(
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeError{location, TypeMismatch{reversed ? subTy : superTy, reversed ? superTy : subTy, "", innerState.errors.front()}});
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
else
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (TableTypeVar* subTable = FFlag::LuauUseCommittingTxnLog ? log.getMutable<TableTypeVar>(subTy) : getMutable<TableTypeVar>(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
switch (subTable->state)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
case TableState::Free:
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(subTy, superMetatable->table);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(subTy, superTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
subTable->boundTo = superTy;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// We know the shape of sealed, unsealed, and generic tables; you can't add a metatable on to any of these.
|
|
|
|
case TableState::Sealed:
|
|
|
|
case TableState::Unsealed:
|
|
|
|
case TableState::Generic:
|
|
|
|
errors.push_back(mismatchError);
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (FFlag::LuauUseCommittingTxnLog ? (log.getMutable<AnyTypeVar>(subTy) || log.getMutable<ErrorTypeVar>(subTy))
|
|
|
|
: (get<AnyTypeVar>(subTy) || get<ErrorTypeVar>(subTy)))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
errors.push_back(mismatchError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Class unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating.
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
if (reversed)
|
|
|
|
std::swap(superTy, subTy);
|
|
|
|
|
|
|
|
auto fail = [&]() {
|
|
|
|
if (!reversed)
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{superTy, subTy}});
|
|
|
|
else
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{subTy, superTy}});
|
|
|
|
};
|
|
|
|
|
|
|
|
const ClassTypeVar* superClass = get<ClassTypeVar>(superTy);
|
|
|
|
if (!superClass)
|
|
|
|
ice("tryUnifyClass invoked with non-class TypeVar");
|
|
|
|
|
|
|
|
if (const ClassTypeVar* subClass = get<ClassTypeVar>(subTy))
|
|
|
|
{
|
|
|
|
switch (variance)
|
|
|
|
{
|
|
|
|
case Covariant:
|
|
|
|
if (!isSubclass(subClass, superClass))
|
|
|
|
return fail();
|
|
|
|
return;
|
|
|
|
case Invariant:
|
|
|
|
if (subClass != superClass)
|
|
|
|
return fail();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ice("Illegal variance setting!");
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (TableTypeVar* subTable = getMutable<TableTypeVar>(subTy))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* A free table is something whose shape we do not exactly know yet.
|
|
|
|
* Thus, it is entirely reasonable that we might discover that it is being used as some class type.
|
|
|
|
* In this case, the free table must indeed be that exact class.
|
|
|
|
* For this to hold, the table must not have any properties that the class does not.
|
|
|
|
* Further, all properties of the table should unify cleanly with the matching class properties.
|
|
|
|
* TODO: What does it mean for the table to have an indexer? (probably failure?)
|
|
|
|
*
|
|
|
|
* Tables that are not free are known to be actual tables.
|
|
|
|
*/
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subTable->state != TableState::Free)
|
2021-10-29 21:25:12 +01:00
|
|
|
return fail();
|
|
|
|
|
|
|
|
bool ok = true;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
for (const auto& [propName, prop] : subTable->props)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
const Property* classProp = lookupClassProp(superClass, propName);
|
|
|
|
if (!classProp)
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
errors.push_back(TypeError{location, UnknownProperty{superTy, propName}});
|
|
|
|
}
|
|
|
|
else
|
2021-11-12 14:27:34 +00:00
|
|
|
{
|
2022-01-06 23:26:14 +00:00
|
|
|
Unifier innerState = makeChildUnifier();
|
2022-01-07 01:46:53 +00:00
|
|
|
innerState.tryUnify_(classProp->type, prop.type);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-06 23:26:14 +00:00
|
|
|
checkChildUnifierTypeMismatch(innerState.errors, propName, reversed ? subTy : superTy, reversed ? superTy : subTy);
|
2021-11-12 14:27:34 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2022-01-06 23:26:14 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (innerState.errors.empty())
|
|
|
|
{
|
|
|
|
log.concat(std::move(innerState.log));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
}
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (innerState.errors.empty())
|
|
|
|
{
|
|
|
|
DEPRECATED_log.concat(std::move(innerState.DEPRECATED_log));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
innerState.DEPRECATED_log.rollback();
|
|
|
|
}
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (subTable->indexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
std::string msg = "Class " + superClass->name + " does not have an indexer";
|
|
|
|
errors.push_back(TypeError{location, GenericError{msg}});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
return;
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.bindTable(subTy, superTy);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(subTable);
|
|
|
|
subTable->boundTo = superTy;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
return fail();
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyIndexer(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(subIndexer.indexType, superIndexer.indexType);
|
|
|
|
tryUnify_(subIndexer.indexResultType, superIndexer.indexResultType);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
static void queueTypePack(std::vector<TypeId>& queue, DenseHashSet<TypePackId>& seenTypePacks, Unifier& state, TypePackId a, TypePackId anyTypePack)
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
2021-11-12 14:27:34 +00:00
|
|
|
a = follow(a);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
|
|
|
if (seenTypePacks.find(a))
|
|
|
|
break;
|
|
|
|
seenTypePacks.insert(a);
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (state.log.getMutable<Unifiable::Free>(a))
|
|
|
|
{
|
|
|
|
state.log.replace(a, Unifiable::Bound{anyTypePack});
|
|
|
|
}
|
|
|
|
else if (auto tp = state.log.getMutable<TypePack>(a))
|
|
|
|
{
|
|
|
|
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
|
|
|
|
if (tp->tail)
|
|
|
|
a = *tp->tail;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else
|
2021-11-05 02:34:35 +00:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (get<Unifiable::Free>(a))
|
|
|
|
{
|
|
|
|
state.DEPRECATED_log(a);
|
|
|
|
*asMutable(a) = Unifiable::Bound{anyTypePack};
|
|
|
|
}
|
|
|
|
else if (auto tp = get<TypePack>(a))
|
|
|
|
{
|
|
|
|
queue.insert(queue.end(), tp->head.begin(), tp->head.end());
|
|
|
|
if (tp->tail)
|
|
|
|
a = *tp->tail;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
2021-11-05 02:34:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool reversed, int subOffset)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
const VariadicTypePack* superVariadic = get<VariadicTypePack>(superTp);
|
|
|
|
|
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
superVariadic = log.getMutable<VariadicTypePack>(superTp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!superVariadic)
|
2021-10-29 21:25:12 +01:00
|
|
|
ice("passed non-variadic pack to tryUnifyVariadics");
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (const VariadicTypePack* subVariadic = get<VariadicTypePack>(subTp))
|
|
|
|
tryUnify_(reversed ? superVariadic->ty : subVariadic->ty, reversed ? subVariadic->ty : superVariadic->ty);
|
2021-10-29 21:25:12 +01:00
|
|
|
else if (get<TypePack>(subTp))
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TypePackIterator subIter = begin(subTp, &log);
|
|
|
|
TypePackIterator subEnd = end(subTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
std::advance(subIter, subOffset);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
while (subIter != subEnd)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(reversed ? superVariadic->ty : *subIter, reversed ? *subIter : superVariadic->ty);
|
|
|
|
++subIter;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (std::optional<TypePackId> maybeTail = subIter.tail())
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
TypePackId tail = follow(*maybeTail);
|
|
|
|
if (get<FreeTypePack>(tail))
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
log.replace(tail, BoundTypePack(superTp));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEPRECATED_log(tail);
|
|
|
|
*asMutable(tail) = BoundTypePack{superTp};
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else if (const VariadicTypePack* vtp = get<VariadicTypePack>(tail))
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
tryUnify_(vtp->ty, superVariadic->ty);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else if (get<Unifiable::Generic>(tail))
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Cannot unify variadic and generic packs"}});
|
|
|
|
}
|
|
|
|
else if (get<Unifiable::Error>(tail))
|
|
|
|
{
|
|
|
|
// Nothing to do here.
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ice("Unknown TypePack kind");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, GenericError{"Failed to unify variadic packs"}});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 02:34:35 +00:00
|
|
|
static void tryUnifyWithAny(std::vector<TypeId>& queue, Unifier& state, DenseHashSet<TypeId>& seen, DenseHashSet<TypePackId>& seenTypePacks,
|
|
|
|
TypeId anyType, TypePackId anyTypePack)
|
|
|
|
{
|
|
|
|
while (!queue.empty())
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeId ty = state.log.follow(queue.back());
|
|
|
|
queue.pop_back();
|
|
|
|
if (seen.find(ty))
|
|
|
|
continue;
|
|
|
|
seen.insert(ty);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (state.log.getMutable<FreeTypeVar>(ty))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
state.log.replace(ty, BoundTypeVar{anyType});
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else if (auto fun = state.log.getMutable<FunctionTypeVar>(ty))
|
|
|
|
{
|
|
|
|
queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
|
|
|
queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack);
|
|
|
|
}
|
|
|
|
else if (auto table = state.log.getMutable<TableTypeVar>(ty))
|
|
|
|
{
|
|
|
|
for (const auto& [_name, prop] : table->props)
|
|
|
|
queue.push_back(prop.type);
|
|
|
|
|
|
|
|
if (table->indexer)
|
|
|
|
{
|
|
|
|
queue.push_back(table->indexer->indexType);
|
|
|
|
queue.push_back(table->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto mt = state.log.getMutable<MetatableTypeVar>(ty))
|
|
|
|
{
|
|
|
|
queue.push_back(mt->table);
|
|
|
|
queue.push_back(mt->metatable);
|
|
|
|
}
|
|
|
|
else if (state.log.getMutable<ClassTypeVar>(ty))
|
|
|
|
{
|
|
|
|
// ClassTypeVars never contain free typevars.
|
|
|
|
}
|
|
|
|
else if (auto union_ = state.log.getMutable<UnionTypeVar>(ty))
|
|
|
|
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
|
|
|
|
else if (auto intersection = state.log.getMutable<IntersectionTypeVar>(ty))
|
|
|
|
queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
} // Primitives, any, errors, and generics are left untouched.
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
TypeId ty = follow(queue.back());
|
|
|
|
queue.pop_back();
|
|
|
|
if (seen.find(ty))
|
|
|
|
continue;
|
|
|
|
seen.insert(ty);
|
|
|
|
|
|
|
|
if (get<FreeTypeVar>(ty))
|
|
|
|
{
|
|
|
|
state.DEPRECATED_log(ty);
|
|
|
|
*asMutable(ty) = BoundTypeVar{anyType};
|
|
|
|
}
|
|
|
|
else if (auto fun = get<FunctionTypeVar>(ty))
|
|
|
|
{
|
|
|
|
queueTypePack(queue, seenTypePacks, state, fun->argTypes, anyTypePack);
|
|
|
|
queueTypePack(queue, seenTypePacks, state, fun->retType, anyTypePack);
|
|
|
|
}
|
|
|
|
else if (auto table = get<TableTypeVar>(ty))
|
|
|
|
{
|
|
|
|
for (const auto& [_name, prop] : table->props)
|
|
|
|
queue.push_back(prop.type);
|
|
|
|
|
|
|
|
if (table->indexer)
|
|
|
|
{
|
|
|
|
queue.push_back(table->indexer->indexType);
|
|
|
|
queue.push_back(table->indexer->indexResultType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto mt = get<MetatableTypeVar>(ty))
|
|
|
|
{
|
|
|
|
queue.push_back(mt->table);
|
|
|
|
queue.push_back(mt->metatable);
|
|
|
|
}
|
|
|
|
else if (get<ClassTypeVar>(ty))
|
|
|
|
{
|
|
|
|
// ClassTypeVars never contain free typevars.
|
|
|
|
}
|
|
|
|
else if (auto union_ = get<UnionTypeVar>(ty))
|
|
|
|
queue.insert(queue.end(), union_->options.begin(), union_->options.end());
|
|
|
|
else if (auto intersection = get<IntersectionTypeVar>(ty))
|
|
|
|
queue.insert(queue.end(), intersection->parts.begin(), intersection->parts.end());
|
|
|
|
else
|
|
|
|
{
|
|
|
|
} // Primitives, any, errors, and generics are left untouched.
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
LUAU_ASSERT(get<AnyTypeVar>(anyTy) || get<ErrorTypeVar>(anyTy));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
// These types are not visited in general loop below
|
2022-01-07 01:46:53 +00:00
|
|
|
if (get<PrimitiveTypeVar>(subTy) || get<AnyTypeVar>(subTy) || get<ClassTypeVar>(subTy))
|
2021-12-03 06:41:04 +00:00
|
|
|
return;
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
const TypePackId anyTypePack = types->addTypePack(TypePackVar{VariadicTypePack{getSingletonTypes().anyType}});
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
const TypePackId anyTP = get<AnyTypeVar>(anyTy) ? anyTypePack : types->addTypePack(TypePackVar{Unifiable::Error{}});
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
std::vector<TypeId> queue = {subTy};
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.tempSeenTy.clear();
|
|
|
|
sharedState.tempSeenTp.clear();
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, getSingletonTypes().anyType, anyTP);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
void Unifier::tryUnifyWithAny(TypePackId subTy, TypePackId anyTp)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
LUAU_ASSERT(get<Unifiable::Error>(anyTp));
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-10 22:05:05 +00:00
|
|
|
const TypeId anyTy = getSingletonTypes().errorRecoveryType();
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
std::vector<TypeId> queue;
|
2021-11-05 15:47:21 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.tempSeenTy.clear();
|
|
|
|
sharedState.tempSeenTp.clear();
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
queueTypePack(queue, sharedState.tempSeenTp, *this, subTy, anyTp);
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
Luau::tryUnifyWithAny(queue, *this, sharedState.tempSeenTy, sharedState.tempSeenTp, anyTy, anyTp);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, Name name)
|
|
|
|
{
|
|
|
|
return Luau::findTablePropertyRespectingMeta(errors, globalScope, lhsType, name, location);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Unifier::occursCheck(TypeId needle, TypeId haystack)
|
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.tempSeenTy.clear();
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
return occursCheck(sharedState.tempSeenTy, needle, haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
void Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
auto check = [&](TypeId tv) {
|
|
|
|
occursCheck(seen, needle, tv);
|
|
|
|
};
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
needle = log.follow(needle);
|
|
|
|
haystack = log.follow(haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (seen.find(haystack))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
seen.insert(haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (log.getMutable<Unifiable::Error>(needle))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!log.getMutable<Unifiable::Free>(needle))
|
|
|
|
ice("Expected needle to be free");
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (needle == haystack)
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
|
|
|
log.replace(needle, *getSingletonTypes().errorRecoveryType());
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (log.getMutable<FreeTypeVar>(haystack))
|
|
|
|
return;
|
|
|
|
else if (auto a = log.getMutable<FunctionTypeVar>(haystack))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!FFlag::LuauOccursCheckOkWithRecursiveFunctions)
|
|
|
|
{
|
|
|
|
for (TypePackIterator it(a->argTypes, &log); it != end(a->argTypes); ++it)
|
|
|
|
check(*it);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
for (TypePackIterator it(a->retType, &log); it != end(a->retType); ++it)
|
|
|
|
check(*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto a = log.getMutable<UnionTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
for (TypeId ty : a->options)
|
|
|
|
check(ty);
|
|
|
|
}
|
|
|
|
else if (auto a = log.getMutable<IntersectionTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
for (TypeId ty : a->parts)
|
2021-10-29 21:25:12 +01:00
|
|
|
check(ty);
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
else
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
needle = follow(needle);
|
|
|
|
haystack = follow(haystack);
|
|
|
|
|
|
|
|
if (seen.find(haystack))
|
|
|
|
return;
|
|
|
|
|
|
|
|
seen.insert(haystack);
|
|
|
|
|
|
|
|
if (get<Unifiable::Error>(needle))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!get<Unifiable::Free>(needle))
|
|
|
|
ice("Expected needle to be free");
|
|
|
|
|
|
|
|
if (needle == haystack)
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
|
|
|
DEPRECATED_log(needle);
|
|
|
|
*asMutable(needle) = *getSingletonTypes().errorRecoveryType();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<FreeTypeVar>(haystack))
|
|
|
|
return;
|
|
|
|
else if (auto a = get<FunctionTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
if (!FFlag::LuauOccursCheckOkWithRecursiveFunctions)
|
|
|
|
{
|
|
|
|
for (TypeId ty : a->argTypes)
|
|
|
|
check(ty);
|
|
|
|
|
|
|
|
for (TypeId ty : a->retType)
|
|
|
|
check(ty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto a = get<UnionTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
for (TypeId ty : a->options)
|
|
|
|
check(ty);
|
|
|
|
}
|
|
|
|
else if (auto a = get<IntersectionTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
for (TypeId ty : a->parts)
|
|
|
|
check(ty);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Unifier::occursCheck(TypePackId needle, TypePackId haystack)
|
|
|
|
{
|
2021-12-03 06:41:04 +00:00
|
|
|
sharedState.tempSeenTp.clear();
|
2021-11-05 15:47:21 +00:00
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
return occursCheck(sharedState.tempSeenTp, needle, haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
2021-12-03 06:41:04 +00:00
|
|
|
void Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, TypePackId haystack)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
{
|
|
|
|
needle = log.follow(needle);
|
|
|
|
haystack = log.follow(haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (seen.find(haystack))
|
|
|
|
return;
|
2021-11-05 02:34:35 +00:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
seen.insert(haystack);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (log.getMutable<Unifiable::Error>(needle))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!get<Unifiable::Free>(needle))
|
|
|
|
ice("Expected needle pack to be free");
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
while (!log.getMutable<ErrorTypeVar>(haystack))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (needle == haystack)
|
|
|
|
{
|
|
|
|
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
|
|
|
log.replace(needle, *getSingletonTypes().errorRecoveryTypePack());
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto a = get<TypePack>(haystack))
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
|
|
|
for (const auto& ty : a->head)
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!FFlag::LuauOccursCheckOkWithRecursiveFunctions)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (auto f = log.getMutable<FunctionTypeVar>(log.follow(ty)))
|
|
|
|
{
|
|
|
|
occursCheck(seen, needle, f->argTypes);
|
|
|
|
occursCheck(seen, needle, f->retType);
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
|
|
|
|
if (a->tail)
|
|
|
|
{
|
|
|
|
haystack = follow(*a->tail);
|
|
|
|
continue;
|
|
|
|
}
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
needle = follow(needle);
|
|
|
|
haystack = follow(haystack);
|
|
|
|
|
|
|
|
if (seen.find(haystack))
|
|
|
|
return;
|
|
|
|
|
|
|
|
seen.insert(haystack);
|
|
|
|
|
|
|
|
if (get<Unifiable::Error>(needle))
|
|
|
|
return;
|
2021-10-29 21:25:12 +01:00
|
|
|
|
2022-01-07 01:46:53 +00:00
|
|
|
if (!get<Unifiable::Free>(needle))
|
|
|
|
ice("Expected needle pack to be free");
|
|
|
|
|
|
|
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, FInt::LuauTypeInferRecursionLimit);
|
|
|
|
|
|
|
|
while (!get<ErrorTypeVar>(haystack))
|
|
|
|
{
|
|
|
|
if (needle == haystack)
|
2021-10-29 21:25:12 +01:00
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
errors.push_back(TypeError{location, OccursCheckFailed{}});
|
|
|
|
DEPRECATED_log(needle);
|
|
|
|
*asMutable(needle) = *getSingletonTypes().errorRecoveryTypePack();
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
2022-01-07 01:46:53 +00:00
|
|
|
|
|
|
|
if (auto a = get<TypePack>(haystack))
|
|
|
|
{
|
|
|
|
if (!FFlag::LuauOccursCheckOkWithRecursiveFunctions)
|
|
|
|
{
|
|
|
|
for (const auto& ty : a->head)
|
|
|
|
{
|
|
|
|
if (auto f = get<FunctionTypeVar>(follow(ty)))
|
|
|
|
{
|
|
|
|
occursCheck(seen, needle, f->argTypes);
|
|
|
|
occursCheck(seen, needle, f->retType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a->tail)
|
|
|
|
{
|
|
|
|
haystack = follow(*a->tail);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Unifier Unifier::makeChildUnifier()
|
|
|
|
{
|
2022-01-07 01:46:53 +00:00
|
|
|
if (FFlag::LuauUseCommittingTxnLog)
|
|
|
|
return Unifier{types, mode, globalScope, log.sharedSeen, location, variance, sharedState, &log};
|
|
|
|
else
|
|
|
|
return Unifier{types, mode, globalScope, DEPRECATED_log.sharedSeen, location, variance, sharedState, &log};
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Unifier::isNonstrictMode() const
|
|
|
|
{
|
|
|
|
return (mode == Mode::Nonstrict) || (mode == Mode::NoCheck);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, TypeId wantedType, TypeId givenType)
|
|
|
|
{
|
|
|
|
if (auto e = hasUnificationTooComplex(innerErrors))
|
|
|
|
errors.push_back(*e);
|
|
|
|
else if (!innerErrors.empty())
|
|
|
|
errors.push_back(TypeError{location, TypeMismatch{wantedType, givenType}});
|
|
|
|
}
|
|
|
|
|
2021-11-12 14:27:34 +00:00
|
|
|
void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const std::string& prop, TypeId wantedType, TypeId givenType)
|
|
|
|
{
|
|
|
|
if (auto e = hasUnificationTooComplex(innerErrors))
|
|
|
|
errors.push_back(*e);
|
|
|
|
else if (!innerErrors.empty())
|
|
|
|
errors.push_back(
|
2021-12-10 22:05:05 +00:00
|
|
|
TypeError{location, TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front()}});
|
2021-11-12 14:27:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-29 21:25:12 +01:00
|
|
|
void Unifier::ice(const std::string& message, const Location& location)
|
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
sharedState.iceHandler->ice(message, location);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Unifier::ice(const std::string& message)
|
|
|
|
{
|
2021-11-05 15:47:21 +00:00
|
|
|
sharedState.iceHandler->ice(message);
|
2021-10-29 21:25:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|