#include "Luau/Subtyping.h"
#include "Luau/Common.h"
#include "Luau/Error.h"
#include "Luau/Normalize.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Scope.h"
#include "Luau/ScopedSeenSet.h"
#include "Luau/Substitution.h"
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeFunction.h"
#include "Luau/TypeIds.h"
#include "Luau/TypePack.h"
#include "Luau/TypePath.h"
#include "Luau/TypeUtils.h"
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSubtypingRecursionLimit, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauMorePreciseErrorSuppression)
LUAU_FASTFLAGVARIABLE(LuauSubtypingMissingPropertiesAsNil)
LUAU_FASTFLAG(LuauTableFreezeCheckIsSubtype)
LUAU_FASTFLAG(LuauUnifyWithSubtyping2)
LUAU_FASTINTVARIABLE(LuauSubtypingIterationLimit, 20000)
LUAU_FASTFLAGVARIABLE(LuauSubtypingReplaceBounds)
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
LUAU_FASTFLAGVARIABLE(LuauFollowGenericBeforeCheckingIfMapped)
namespace Luau
{
bool SubtypingReasoning::operator==(const SubtypingReasoning& other) const
{
return subPath == other.subPath && superPath == other.superPath && variance == other.variance;
}
size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const
{
return TypePath::PathHash()(r.subPath) ^ (TypePath::PathHash()(r.superPath) << 1) ^ (static_cast<size_t>(r.variance) << 1);
}
MappedGenericEnvironment::MappedGenericFrame::MappedGenericFrame(
DenseHashMap<TypePackId, std::optional<TypePackId>> mappings,
const std::optional<size_t> parentScopeIndex
)
: mappings(std::move(mappings))
, parentScopeIndex(parentScopeIndex)
{
}
MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPack(TypePackId genericTp) const
{
genericTp = follow(genericTp);
std::optional<size_t> currentFrameIndex = currentScopeIndex;
while (currentFrameIndex)
{
const MappedGenericFrame& currentFrame = frames[*currentFrameIndex];
if (const auto mappedPack = currentFrame.mappings.find(genericTp))
{
if (*mappedPack)
return mappedPack->value();
else
return Unmapped{*currentFrameIndex};
}
currentFrameIndex = currentFrame.parentScopeIndex;
}
if (currentScopeIndex)
{
const MappedGenericFrame& baseFrame = frames[*currentScopeIndex];
std::vector<size_t> toCheck = std::vector(baseFrame.children.begin(), baseFrame.children.end());
while (!toCheck.empty())
{
const size_t currIndex = toCheck.back();
toCheck.pop_back();
const MappedGenericFrame& currentFrame = frames[currIndex];
if (const auto mappedPack = currentFrame.mappings.find(genericTp))
{
if (*mappedPack)
return mappedPack->value();
else
return Unmapped{currIndex};
}
toCheck.insert(toCheck.end(), currentFrame.children.begin(), currentFrame.children.end());
}
}
return NotBindable{};
}
void MappedGenericEnvironment::pushFrame(const std::vector<TypePackId>& genericTps)
{
DenseHashMap<TypePackId, std::optional<TypePackId>> mappings{nullptr};
for (TypePackId tp : genericTps)
mappings[tp] = std::nullopt;
frames.emplace_back(std::move(mappings), currentScopeIndex);
const size_t newFrameIndex = frames.size() - 1;
if (currentScopeIndex)
frames[*currentScopeIndex].children.insert(newFrameIndex);
currentScopeIndex = newFrameIndex;
}
void MappedGenericEnvironment::popFrame()
{
LUAU_ASSERT(currentScopeIndex);
if (currentScopeIndex)
{
const std::optional<size_t> newFrameIndex = frames[*currentScopeIndex].parentScopeIndex;
currentScopeIndex = newFrameIndex.value_or(0);
}
}
bool MappedGenericEnvironment::bindGeneric(TypePackId genericTp, TypePackId bindeeTp)
{
if (genericTp == bindeeTp)
return true;
if (!get<GenericTypePack>(genericTp))
{
LUAU_ASSERT(!"bindGeneric should not be called with a non-generic type pack");
return false;
}
const LookupResult lookupResult = lookupGenericPack(genericTp);
if (const Unmapped* unmapped = get_if<Unmapped>(&lookupResult))
{
frames[unmapped->scopeIndex].mappings[genericTp] = bindeeTp;
return true;
}
else
{
return false;
}
}
template<typename TID>
static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes)
{
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
return;
for (const SubtypingReasoning& reasoning : result.reasoning)
{
LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes));
LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes));
}
}
template<typename TID>
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena)
{
if (!FFlag::DebugLuauSubtypingCheckPathValidity)
return;
for (const SubtypingReasoning& reasoning : result.reasoning)
{
LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, arena));
LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, arena));
}
}
template<>
void assertReasoningValid<TableIndexer>(TableIndexer, TableIndexer, const SubtypingResult&, NotNull<BuiltinTypes>, NotNull<TypeArena>)
{
}
static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const SubtypingReasonings& b)
{
SubtypingReasonings result{kEmptyReasoning};
for (const SubtypingReasoning& r : a)
{
if (r.variance == SubtypingVariance::Invariant)
result.insert(r);
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
{
SubtypingReasoning inverseReasoning = SubtypingReasoning{
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant
};
if (b.contains(inverseReasoning))
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
else
result.insert(r);
}
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
for (const SubtypingReasoning& r : b)
{
if (r.variance == SubtypingVariance::Invariant)
result.insert(r);
else if (r.variance == SubtypingVariance::Covariant || r.variance == SubtypingVariance::Contravariant)
{
SubtypingReasoning inverseReasoning = SubtypingReasoning{
r.subPath, r.superPath, r.variance == SubtypingVariance::Covariant ? SubtypingVariance::Contravariant : SubtypingVariance::Covariant
};
if (a.contains(inverseReasoning))
result.insert(SubtypingReasoning{r.subPath, r.superPath, SubtypingVariance::Invariant});
else
result.insert(r);
}
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
return result;
}
SubtypingResult& SubtypingResult::andAlso(SubtypingResult other, SubtypingSuppressionPolicy policy)
{
if (!other.isSubtype)
{
if (isSubtype)
reasoning = std::move(other.reasoning);
else
reasoning = mergeReasonings(reasoning, other.reasoning);
}
isSubtype &= other.isSubtype;
if (FFlag::LuauMorePreciseErrorSuppression)
{
if (policy == SubtypingSuppressionPolicy::All)
isErrorSuppressing &= other.isErrorSuppressing;
else
isErrorSuppressing |= other.isErrorSuppressing;
}
normalizationTooComplex |= other.normalizationTooComplex;
isCacheable &= other.isCacheable;
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end());
assumedConstraints.insert(assumedConstraints.end(), other.assumedConstraints.begin(), other.assumedConstraints.end());
return *this;
}
SubtypingResult& SubtypingResult::orElse(SubtypingResult other)
{
if (!isSubtype)
{
if (other.isSubtype)
{
reasoning.clear();
assumedConstraints = std::move(other.assumedConstraints);
}
else
{
reasoning = mergeReasonings(reasoning, other.reasoning);
if (FFlag::LuauMorePreciseErrorSuppression)
isErrorSuppressing |= other.isErrorSuppressing;
}
}
else if (FFlag::LuauUnifyWithSubtyping2 && other.isSubtype)
{
assumedConstraints = std::move(other.assumedConstraints);
}
isSubtype |= other.isSubtype;
normalizationTooComplex |= other.normalizationTooComplex;
isCacheable &= other.isCacheable;
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end());
return *this;
}
SubtypingResult& SubtypingResult::withBothComponent(TypePath::Component component)
{
return withSubComponent(component).withSuperComponent(std::move(component));
}
SubtypingResult& SubtypingResult::withSubComponent(TypePath::Component component)
{
if (reasoning.empty())
reasoning.insert(SubtypingReasoning{Path(std::move(component)), TypePath::kEmpty});
else
{
for (auto& r : reasoning)
r.subPath = r.subPath.push_front(component);
}
return *this;
}
SubtypingResult& SubtypingResult::withSuperComponent(TypePath::Component component)
{
if (reasoning.empty())
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, Path(std::move(component))});
else
{
for (auto& r : reasoning)
r.superPath = r.superPath.push_front(component);
}
return *this;
}
SubtypingResult& SubtypingResult::withBothPath(TypePath::Path path)
{
return withSubPath(path).withSuperPath(std::move(path));
}
SubtypingResult& SubtypingResult::withSubPath(TypePath::Path path)
{
if (reasoning.empty())
reasoning.insert(SubtypingReasoning{std::move(path), TypePath::kEmpty});
else
{
for (auto& r : reasoning)
r.subPath = path.append(r.subPath);
}
return *this;
}
SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
{
if (reasoning.empty())
reasoning.insert(SubtypingReasoning{TypePath::kEmpty, std::move(path)});
else
{
for (auto& r : reasoning)
r.superPath = path.append(r.superPath);
}
return *this;
}
SubtypingResult& SubtypingResult::withErrors(ErrorVec& err)
{
for (TypeError& e : err)
errors.emplace_back(e);
return *this;
}
SubtypingResult& SubtypingResult::withError(TypeError err)
{
errors.push_back(std::move(err));
return *this;
}
SubtypingResult& SubtypingResult::withAssumedConstraint(ConstraintV constraint)
{
assumedConstraints.push_back(std::move(constraint));
return *this;
}
SubtypingResult SubtypingResult::negate(const SubtypingResult& result)
{
return SubtypingResult{
!result.isSubtype,
result.normalizationTooComplex,
};
}
struct ApplyMappedGenerics : Substitution
{
NotNull<BuiltinTypes> builtinTypes;
NotNull<TypeArena> arena;
NotNull<InternalErrorReporter> iceReporter;
NotNull<SubtypingEnvironment> env;
ApplyMappedGenerics(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
SubtypingEnvironment& env,
NotNull<InternalErrorReporter> iceReporter
)
: Substitution(TxnLog::empty(), arena)
, builtinTypes(builtinTypes)
, arena(arena)
, iceReporter(iceReporter.get())
, env(NotNull{&env})
{
}
bool isDirty(TypeId ty) override
{
return env->containsMappedType(ty);
}
bool isDirty(TypePackId tp) override
{
return env->containsMappedPack(tp);
}
TypeId clean(TypeId ty) override
{
const auto& [lowerBound, upperBound] = env->getMappedTypeBounds(ty, NotNull{iceReporter});
if (upperBound.empty() && lowerBound.empty())
{
return builtinTypes->unknownType;
}
else if (!upperBound.empty())
{
if (FFlag::LuauSubtypingReplaceBounds)
{
IntersectionBuilder ib{arena, builtinTypes};
for (TypeId ub : upperBound)
{
if (!get<GenericType>(ub))
ib.add(ub);
}
return ib.build();
}
else
{
TypeIds boundsToUse;
for (TypeId ub : upperBound)
{
if (!get<GenericType>(ub))
boundsToUse.insert(ub);
}
if (boundsToUse.empty())
{
return builtinTypes->unknownType;
}
if (boundsToUse.size() == 1)
return *boundsToUse.begin();
return arena->addType(IntersectionType{boundsToUse.take()});
}
}
else if (!lowerBound.empty())
{
if (FFlag::LuauSubtypingReplaceBounds)
{
UnionBuilder ub{arena, builtinTypes};
for (TypeId lb : lowerBound)
{
if (!get<GenericType>(lb))
ub.add(lb);
}
return ub.build();
}
else
{
TypeIds boundsToUse;
for (TypeId lb : lowerBound)
{
if (!get<GenericType>(lb))
boundsToUse.insert(lb);
}
if (boundsToUse.empty())
{
return builtinTypes->unknownType;
}
else if (lowerBound.size() == 1)
return *boundsToUse.begin();
else
return arena->addType(UnionType{boundsToUse.take()});
}
}
else
{
LUAU_ASSERT(!"Unreachable path");
return builtinTypes->unknownType;
}
}
TypePackId clean(TypePackId tp) override
{
const MappedGenericEnvironment::LookupResult result = env->lookupGenericPack(tp);
if (const TypePackId* mappedGen = get_if<TypePackId>(&result))
return *mappedGen;
LUAU_ASSERT(!"Unreachable");
return builtinTypes->anyTypePack;
}
bool ignoreChildren(TypeId ty) override
{
if (get<ExternType>(ty))
return true;
if (const FunctionType* f = get<FunctionType>(ty))
{
for (TypeId g : f->generics)
{
if (FFlag::LuauFollowGenericBeforeCheckingIfMapped)
g = follow(g);
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env->mappedGenerics.find(g); bounds && !bounds->empty())
return true;
}
}
return ty->persistent;
}
bool ignoreChildren(TypePackId ty) override
{
return ty->persistent;
}
};
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
TypeId ty,
NotNull<InternalErrorReporter> iceReporter
)
{
ApplyMappedGenerics amg{builtinTypes, arena, *this, iceReporter};
return amg.substitute(ty);
}
std::optional<TypeId> SubtypingEnvironment::tryFindSubstitution(TypeId ty) const
{
if (const TypeId* it = substitutions.find(ty))
return *it;
if (parent)
return parent->tryFindSubstitution(ty);
return std::nullopt;
}
const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const
{
if (const auto it = seenSetCache.find(subAndSuper))
return it;
if (parent)
return parent->tryFindSubtypingResult(subAndSuper);
return nullptr;
}
bool SubtypingEnvironment::containsMappedType(TypeId ty) const
{
ty = follow(ty);
if (const auto bounds = mappedGenerics.find(ty); bounds && !bounds->empty())
return true;
if (parent)
return parent->containsMappedType(ty);
return false;
}
bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const
{
if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if<TypePackId>(&lookupResult))
return true;
if (parent)
return parent->containsMappedPack(tp);
return false;
}
SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty, NotNull<InternalErrorReporter> iceReporter)
{
ty = follow(ty);
std::vector<GenericBounds>* bounds = mappedGenerics.find(ty);
if (bounds && !bounds->empty())
return bounds->back();
if (parent)
return parent->getMappedTypeBounds(ty, iceReporter);
LUAU_ASSERT(!"Use containsMappedType before asking for bounds!");
iceReporter->ice("Trying to access bounds for a type with no in-scope bounds");
}
MappedGenericEnvironment::LookupResult SubtypingEnvironment::lookupGenericPack(TypePackId tp) const
{
MappedGenericEnvironment::LookupResult result = mappedGenericPacks.lookupGenericPack(tp);
if (get_if<TypePackId>(&result))
return result;
else if (parent)
return parent->lookupGenericPack(tp);
else
return result;
}
Subtyping::Subtyping(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> typeArena,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> iceReporter
)
: builtinTypes(builtinTypes)
, arena(typeArena)
, normalizer(normalizer)
, typeFunctionRuntime(typeFunctionRuntime)
, iceReporter(iceReporter)
{
}
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope)
{
SubtypingEnvironment env;
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
if (result.normalizationTooComplex)
{
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
for (const auto& [_, bounds] : env.mappedGenerics)
LUAU_ASSERT(bounds.empty());
if (result.isCacheable)
resultCache[{subTy, superTy}] = result;
return result;
}
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope, const std::vector<TypeId>& bindableGenerics)
{
const std::vector<TypePackId> bindableGenericPacks;
return isSubtype(subTp, superTp, scope, bindableGenerics, bindableGenericPacks);
}
SubtypingResult Subtyping::isSubtype(
TypePackId subTp,
TypePackId superTp,
NotNull<Scope> scope,
const std::vector<TypeId>& bindableGenerics,
const std::vector<TypePackId>& bindableGenericPacks
)
{
SubtypingEnvironment env;
for (TypeId g : bindableGenerics)
env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}};
env.mappedGenericPacks.pushFrame(bindableGenericPacks);
SubtypingResult result = isCovariantWith(env, subTp, superTp, scope);
for (TypeId bg : bindableGenerics)
{
bg = follow(bg);
LUAU_ASSERT(env.mappedGenerics.contains(bg));
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env.mappedGenerics.find(bg))
{
LUAU_ASSERT(bounds->size() == 1);
if (bounds->empty())
continue;
if (const GenericType* gen = get<GenericType>(bg))
result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name));
}
}
return result;
}
SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
{
const std::pair<TypeId, TypeId> p{subTy, superTy};
if (result.isCacheable)
resultCache[p] = result;
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope)
{
NonExceptionalRecursionLimiter nerl(&normalizer->sharedState->counters.recursionCount);
if (!nerl.isOk(DFInt::LuauSubtypingRecursionLimit))
return SubtypingResult{false, true};
if (FFlag::LuauUnifyWithSubtyping2)
{
env.iterationCount++;
if (FInt::LuauSubtypingIterationLimit > 0 && env.iterationCount >= FInt::LuauSubtypingIterationLimit)
return SubtypingResult{false, true};
}
subTy = follow(subTy);
superTy = follow(superTy);
if (std::optional<TypeId> subIt = env.tryFindSubstitution(subTy); subIt && *subIt)
subTy = *subIt;
if (std::optional<TypeId> superIt = env.tryFindSubstitution(superTy); superIt && *superIt)
subTy = *superIt;
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
if (cachedResult)
{
return *cachedResult;
}
cachedResult = env.tryFindSubtypingResult({subTy, superTy});
if (cachedResult)
{
return *cachedResult;
}
if (subTy == superTy)
return {true};
std::pair<TypeId, TypeId> typePair{subTy, superTy};
if (!seenTypes.insert(typePair))
{
SubtypingResult res;
res.isSubtype = true;
res.isCacheable = false;
env.seenSetCache[typePair] = res;
return res;
}
ScopedSeenSet ssp{seenTypes, typePair};
if (!FFlag::LuauUnifyWithSubtyping2)
{
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
return isCovariantWith(env, builtinTypes->neverType, superTy, scope);
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
return isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
}
SubtypingResult result;
if (FFlag::LuauUnifyWithSubtyping2 && get2<FreeType, FreeType>(subTy, superTy))
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto superFree = get<FreeType>(superTy); FFlag::LuauUnifyWithSubtyping2 && superFree)
{
result = isCovariantWith(env, subTy, superFree->upperBound, scope);
if (result.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto subFree = get<FreeType>(subTy); FFlag::LuauUnifyWithSubtyping2 && subFree)
{
if (isCovariantWith(env, subFree->lowerBound, superTy, scope).isSubtype)
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else
result = {false};
}
else if (FFlag::LuauUnifyWithSubtyping2 && (is<BlockedType>(subTy) || is<BlockedType>(superTy)))
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto subGeneric = get<GenericType>(subTy); FFlag::LuauUnifyWithSubtyping2 && subGeneric && subsumes(subGeneric->scope, scope))
return isCovariantWith(env, builtinTypes->neverType, superTy, scope);
else if (auto superGeneric = get<GenericType>(superTy); FFlag::LuauUnifyWithSubtyping2 && superGeneric && subsumes(superGeneric->scope, scope))
return isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
else if (get<AnyType>(superTy))
result = {true};
else if (get<AnyType>(subTy) && get<UnknownType>(superTy))
result = {true};
else if (get<AnyType>(subTy))
{
result =
isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope));
if (FFlag::LuauMorePreciseErrorSuppression)
result.isErrorSuppressing = true;
}
else if (get<UnknownType>(superTy) && !get<UnionType>(subTy) && !get<IntersectionType>(subTy))
{
LUAU_ASSERT(!get<AnyType>(subTy));
LUAU_ASSERT(!get<UnionType>(subTy));
LUAU_ASSERT(!get<IntersectionType>(subTy));
bool errorSuppressing = nullptr != get<ErrorType>(subTy);
if (FFlag::LuauMorePreciseErrorSuppression)
{
result.isSubtype = !errorSuppressing;
result.isErrorSuppressing = errorSuppressing;
}
else
result = {!errorSuppressing};
}
else if (get<NeverType>(subTy))
result = {true};
else if (get<ErrorType>(superTy))
result = {false};
else if (get<ErrorType>(subTy))
{
result = {true};
if (FFlag::LuauMorePreciseErrorSuppression)
result.isErrorSuppressing = true;
}
else if (auto subTypeFunctionInstance = get<TypeFunctionInstanceType>(subTy))
{
bool mappedGenericsApplied = false;
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy, iceReporter))
{
mappedGenericsApplied = *substSubTy != subTy;
subTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSubTy);
}
result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope);
result.isCacheable = !mappedGenericsApplied;
}
else if (auto superTypeFunctionInstance = get<TypeFunctionInstanceType>(superTy))
{
bool mappedGenericsApplied = false;
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy, iceReporter))
{
mappedGenericsApplied = *substSuperTy != superTy;
superTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSuperTy);
}
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
result.isCacheable = !mappedGenericsApplied;
}
else if (get<GenericType>(subTy) || get<GenericType>(superTy))
{
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
else if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
{
bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok;
result.isCacheable = false;
}
}
else if (auto subUnion = get<UnionType>(subTy))
result = isCovariantWith(env, subUnion, superTy, scope);
else if (auto superUnion = get<UnionType>(superTy))
{
result = isCovariantWith(env, subTy, superUnion, scope);
if (!result.isSubtype && !result.normalizationTooComplex)
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
}
else if (auto superIntersection = get<IntersectionType>(superTy))
result = isCovariantWith(env, subTy, superIntersection, scope);
else if (auto subIntersection = get<IntersectionType>(subTy))
{
result = isCovariantWith(env, subIntersection, superTy, scope);
if (!result.isSubtype && !result.normalizationTooComplex)
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
}
else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); !FFlag::LuauUnifyWithSubtyping2 && pair)
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto superFree = get<FreeType>(superTy); !FFlag::LuauUnifyWithSubtyping2 && superFree)
{
result = isCovariantWith(env, subTy, superFree->upperBound, scope);
if (result.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else if (auto subFree = get<FreeType>(subTy); !FFlag::LuauUnifyWithSubtyping2 && subFree)
{
if (FFlag::LuauMorePreciseErrorSuppression)
{
SubtypingResult r = isCovariantWith(env, subFree->lowerBound, superTy, scope);
result.isSubtype = r.isSubtype;
result.isErrorSuppressing = r.isErrorSuppressing;
if (r.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else
{
if (isCovariantWith(env, subFree->lowerBound, superTy, scope).isSubtype)
{
result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
else
result = {false};
}
}
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
{
result = isContravariantWith(env, p.first->ty, p.second->ty, scope).withBothComponent(TypePath::TypeField::Negated);
}
else if (auto subNegation = get<NegationType>(subTy))
{
result = isCovariantWith(env, subNegation, superTy, scope);
if (!result.isSubtype && !result.normalizationTooComplex)
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
}
else if (auto superNegation = get<NegationType>(superTy))
{
result = isCovariantWith(env, subTy, superNegation, scope);
if (!result.isSubtype && !result.normalizationTooComplex)
result = trySemanticSubtyping(env, subTy, superTy, scope, result);
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, superTy))
{
auto [subFunction, superPrimitive] = p;
result.isSubtype = superPrimitive->type == PrimitiveType::Function;
}
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<TableType, TableType>(subTy, superTy))
{
const bool forceCovariantTest = uniqueTypes != nullptr && uniqueTypes->contains(subTy);
result = isCovariantWith(env, p.first, p.second, forceCovariantTest, scope);
if (FFlag::LuauUnifyWithSubtyping2)
{
if (result.isSubtype && !p.first->indexer && p.second->indexer && p.first->state != TableState::Sealed)
{
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
}
}
}
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (FFlag::LuauTableFreezeCheckIsSubtype && get2<MetatableType, PrimitiveType>(subTy, superTy))
{
auto p = get2<MetatableType, PrimitiveType>(subTy, superTy);
result = isCovariantWith(env, p, scope);
}
else if (auto p = get2<ExternType, ExternType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<ExternType, TableType>(subTy, superTy))
result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope);
else if (auto p = get2<TableType, PrimitiveType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope);
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
return cache(env, std::move(result), subTy, superTy);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
{
NonExceptionalRecursionLimiter nerl{&normalizer->sharedState->counters.recursionCount};
if (!nerl.isOk(DFInt::LuauSubtypingRecursionLimit))
return SubtypingResult{false, true};
subTp = follow(subTp);
superTp = follow(superTp);
std::pair<TypePackId, TypePackId> typePair = {subTp, superTp};
if (!seenPacks.insert(typePair))
return SubtypingResult{true, false, false};
ScopedSeenSet<Subtyping::SeenTypePackSet, std::pair<TypePackId, TypePackId>> popper{seenPacks, std::move(typePair)};
auto [subHead, subTail] = flatten(subTp);
auto [superHead, superTail] = flatten(superTp);
const size_t headSize = std::min(subHead.size(), superHead.size());
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
result->isSubtype = true;
if (subTp == superTp)
return {true};
for (size_t i = 0; i < headSize; ++i)
result->andAlso(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
if (subHead.size() < superHead.size())
{
if (subTail)
{
auto earlyExit = isSubTailCovariantWith(env, *result, subTp, *subTail, superTp, headSize, superHead, superTail, scope);
if (earlyExit == EarlyExit::Yes)
return *result;
}
else
{
result->andAlso({false});
return *result;
}
}
else if (subHead.size() > superHead.size())
{
if (superTail)
{
auto earlyExit = isCovariantWithSuperTail(env, *result, subTp, headSize, subHead, subTail, superTp, *superTail, scope);
if (earlyExit == EarlyExit::Yes)
return *result;
}
else
return {false};
}
if (subTail && superTail)
{
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
{
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
}
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
{
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
}
else if (auto p = get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
{
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
}
else if (auto p = get2<GenericTypePack, VariadicTypePack>(*subTail, *superTail))
{
result->andAlso(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second));
}
else if (FFlag::LuauUnifyWithSubtyping2 && (is<FreeTypePack>(*subTail) || is<FreeTypePack>(*superTail)))
{
result->andAlso(
SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail).withAssumedConstraint(PackSubtypeConstraint{*subTail, *superTail})
);
}
else if (get<ErrorTypePack>(*subTail) || get<ErrorTypePack>(*superTail))
result->andAlso(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
else if (get<FreeTypePack>(*subTail) || get<FreeTypePack>(*superTail))
{
return SubtypingResult{true}
.withBothComponent(TypePath::PackField::Tail)
.withAssumedConstraint(PackSubtypeConstraint{*subTail, *superTail});
}
else
return SubtypingResult{false}
.withBothComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}})
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
}
else if (subTail)
{
if (get<VariadicTypePack>(*subTail))
{
return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail);
}
else if (auto g = get<GenericTypePack>(*subTail))
{
return isTailCovariantWithTail(env, scope, *subTail, g, Nothing{});
}
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(*subTail))
{
return SubtypingResult{true}
.withBothComponent(TypePath::PackField::Tail)
.withAssumedConstraint(PackSubtypeConstraint{*subTail, builtinTypes->emptyTypePack});
}
else
return SubtypingResult{false}
.withSubComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
}
else if (superTail)
{
if (get<VariadicTypePack>(*superTail))
{
}
else if (auto g = get<GenericTypePack>(*superTail))
{
result->andAlso(isTailCovariantWithTail(env, scope, Nothing{}, *superTail, g));
}
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(*superTail))
{
result->andAlso(
SubtypingResult{true}
.withBothComponent(TypePath::PackField::Tail)
.withAssumedConstraint(PackSubtypeConstraint{builtinTypes->emptyTypePack, *superTail})
);
}
else
return SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
}
assertReasoningValid(subTp, superTp, *result, builtinTypes, arena);
return *result;
}
Subtyping::EarlyExit Subtyping::isSubTailCovariantWith(
SubtypingEnvironment& env,
SubtypingResult& outputResult,
TypePackId subTp,
TypePackId subTail,
TypePackId superTp,
size_t superHeadStartIndex,
const std::vector<TypeId>& superHead,
std::optional<TypePackId> superTail,
NotNull<Scope> scope
)
{
if (auto vt = get<VariadicTypePack>(subTail))
{
for (size_t i = superHeadStartIndex; i < superHead.size(); ++i)
outputResult.andAlso(isCovariantWith(env, vt->ty, superHead[i], scope)
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}));
return EarlyExit::No;
}
else if (get<GenericTypePack>(subTail))
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTail);
SubtypingResult result;
if (get_if<MappedGenericEnvironment::NotBindable>(&lookupResult))
result = SubtypingResult{false, false, false}
.withSubComponent(TypePath::PackField::Tail)
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
else
{
TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena);
if (const TypePackId* mappedGen = get_if<TypePackId>(&lookupResult))
{
TypePackId subTpToCompare = *mappedGen;
const TypePack* tp = get<TypePack>(*mappedGen);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(follow(tp->tail)) : nullptr; vtp && vtp->hidden)
subTpToCompare = arena->addTypePack(tp->head);
result = isCovariantWith(env, subTpToCompare, superTailPack, scope)
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}}))
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::Unmapped>(&lookupResult));
bool ok = env.mappedGenericPacks.bindGeneric(subTail, superTailPack);
result = SubtypingResult{ok, false, false}
.withSubComponent(TypePath::PackField::Tail)
.withSuperComponent(TypePath::PackSlice{superHeadStartIndex});
}
}
outputResult.andAlso(result);
return EarlyExit::Yes;
}
else if (get<ErrorTypePack>(subTail))
{
outputResult = SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
return EarlyExit::Yes;
}
else if (FFlag::LuauUnifyWithSubtyping2 && get<FreeTypePack>(subTail))
{
TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena);
outputResult.andAlso(
SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail).withAssumedConstraint(PackSubtypeConstraint{subTail, superTailPack})
);
return EarlyExit::Yes;
}
else
{
outputResult =
SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail).withError({scope->location, UnexpectedTypePackInSubtyping{subTail}});
return EarlyExit::Yes;
}
}
Subtyping::EarlyExit Subtyping::isCovariantWithSuperTail(
SubtypingEnvironment& env,
SubtypingResult& outputResult,
TypePackId subTp,
size_t subHeadStartIndex,
const std::vector<TypeId>& subHead,
std::optional<TypePackId> subTail,
TypePackId superTp,
TypePackId superTail,
NotNull<Scope> scope
)
{
if (auto vt = get<VariadicTypePack>(superTail))
{
for (size_t i = subHeadStartIndex; i < subHead.size(); ++i)
outputResult.andAlso(isCovariantWith(env, subHead[i], vt->ty, scope)
.withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})
.withSuperPath(TypePath::PathBuilder().tail().variadic().build()));
return EarlyExit::No;
}
else if (get<GenericTypePack>(superTail))
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTail);
SubtypingResult result;
if (get_if<MappedGenericEnvironment::NotBindable>(&lookupResult))
result = SubtypingResult{false, false, false}
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
.withSuperComponent(TypePath::PackField::Tail);
else
{
TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena);
if (const TypePackId* mappedGen = get_if<TypePackId>(&lookupResult))
{
TypePackId superTpToCompare = *mappedGen;
const TypePack* tp = get<TypePack>(*mappedGen);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(follow(tp->tail)) : nullptr; vtp && vtp->hidden)
superTpToCompare = arena->addTypePack(tp->head);
result = isCovariantWith(env, subTailPack, superTpToCompare, scope)
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}}));
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::Unmapped>(&lookupResult));
bool ok = env.mappedGenericPacks.bindGeneric(superTail, subTailPack);
result = SubtypingResult{ok, false, false}
.withSubComponent(TypePath::PackSlice{subHeadStartIndex})
.withSuperComponent(TypePath::PackField::Tail);
}
}
outputResult.andAlso(result);
return EarlyExit::Yes;
}
else if (get<ErrorTypePack>(superTail))
{
outputResult = SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
return EarlyExit::Yes;
}
else if (FFlag::LuauUnifyWithSubtyping2 && is<FreeTypePack>(superTail))
{
TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena);
outputResult.andAlso(
SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail).withAssumedConstraint({PackSubtypeConstraint{subTailPack, superTail}})
);
return EarlyExit::Yes;
}
else
{
outputResult = SubtypingResult{false}
.withSuperComponent(TypePath::PackField::Tail)
.withError({scope->location, UnexpectedTypePackInSubtyping{superTail}});
return EarlyExit::Yes;
}
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
TypePackId subTp,
const VariadicTypePack* sub,
TypePackId superTp,
const VariadicTypePack* super
)
{
return isCovariantWith(env, sub->ty, super->ty, scope)
.withBothComponent(TypePath::TypeField::Variadic)
.withBothComponent(TypePath::PackField::Tail);
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
TypePackId subTp,
const GenericTypePack* sub,
TypePackId superTp,
const GenericTypePack* super
)
{
MappedGenericEnvironment::LookupResult subLookupResult = env.lookupGenericPack(subTp);
MappedGenericEnvironment::LookupResult superLookupResult = env.lookupGenericPack(superTp);
if (const TypePackId* currMapping = get_if<TypePackId>(&subLookupResult))
{
return isCovariantWith(env, *currMapping, superTp, scope)
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))
.withSuperComponent(TypePath::PackField::Tail);
}
else if (get_if<MappedGenericEnvironment::Unmapped>(&subLookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp);
return SubtypingResult{ok, false, false}.withBothComponent(TypePath::PackField::Tail);
}
else if (const TypePackId* currMapping = get_if<TypePackId>(&superLookupResult))
{
return isCovariantWith(env, subTp, *currMapping, scope)
.withSubComponent(TypePath::PackField::Tail)
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
}
else if (get_if<MappedGenericEnvironment::Unmapped>(&superLookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp);
return SubtypingResult{ok, false, false}.withBothComponent(TypePath::PackField::Tail);
}
else
{
return SubtypingResult{subTp == superTp, false, false}.withBothComponent(
TypePath::PackField::Tail
);
}
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
TypePackId subTp,
const VariadicTypePack* sub,
TypePackId superTp,
const GenericTypePack* super
)
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp);
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
{
return isCovariantWith(env, subTp, *currMapping, scope)
.withSubComponent(TypePath::PackField::Tail)
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
}
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp);
return SubtypingResult{ok, false, false}.withBothComponent(TypePath::PackField::Tail);
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
return SubtypingResult{false, false, false}.withBothComponent(TypePath::PackField::Tail);
}
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
TypePackId subTp,
const GenericTypePack* sub,
TypePackId superTp,
const VariadicTypePack* super
)
{
if (TypeId t = follow(super->ty); get<AnyType>(t) || get<UnknownType>(t))
{
return SubtypingResult{true};
}
else
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp);
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
{
return isCovariantWith(env, *currMapping, superTp, scope)
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))
.withSuperComponent(TypePath::PackField::Tail);
}
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp);
return SubtypingResult{ok, false, false}.withBothComponent(TypePath::PackField::Tail);
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
return SubtypingResult{false, false, false}.withBothComponent(TypePath::PackField::Tail);
}
}
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
TypePackId subTp,
const GenericTypePack* sub,
Nothing
)
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp);
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
return isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope)
.withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(subTp, builtinTypes->emptyTypePack);
return SubtypingResult{ok, false, false}.withSubComponent(TypePath::PackField::Tail);
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
return SubtypingResult{false, false, false}.withSubComponent(TypePath::PackField::Tail);
}
}
SubtypingResult Subtyping::isTailCovariantWithTail(
SubtypingEnvironment& env,
NotNull<Scope> scope,
Nothing,
TypePackId superTp,
const GenericTypePack* super
)
{
MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp);
if (const TypePackId* currMapping = get_if<TypePackId>(&lookupResult))
return isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope)
.withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}));
else if (get_if<MappedGenericEnvironment::Unmapped>(&lookupResult))
{
bool ok = env.mappedGenericPacks.bindGeneric(superTp, builtinTypes->emptyTypePack);
return SubtypingResult{ok, false, false}.withSuperComponent(TypePath::PackField::Tail);
}
else
{
LUAU_ASSERT(get_if<MappedGenericEnvironment::NotBindable>(&lookupResult));
return SubtypingResult{false, false, false}.withSuperComponent(TypePath::PackField::Tail);
}
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy subTy, SuperTy superTy, NotNull<Scope> scope)
{
SubtypingResult result = isCovariantWith(env, superTy, subTy, scope);
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Contravariant});
else
{
for (auto& reasoning : result.reasoning)
{
std::swap(reasoning.subPath, reasoning.superPath);
if (reasoning.variance == SubtypingVariance::Covariant)
reasoning.variance = SubtypingVariance::Contravariant;
else if (reasoning.variance == SubtypingVariance::Contravariant)
reasoning.variance = SubtypingVariance::Covariant;
}
}
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
return result;
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy subTy, SuperTy superTy, NotNull<Scope> scope)
{
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
result.andAlso(isContravariantWith(env, subTy, superTy, scope));
if (result.reasoning.empty())
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
else
{
for (auto& reasoning : result.reasoning)
reasoning.variance = SubtypingVariance::Invariant;
}
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
return result;
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isCovariantWith(env, pair.first, pair.second, scope);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isContravariantWith(env, pair.first, pair.second, scope);
}
template<typename SubTy, typename SuperTy>
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair, NotNull<Scope> scope)
{
return isInvariantWith(env, pair.first, pair.second);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull<Scope> scope)
{
SubtypingResult result{false};
size_t index = 0;
for (TypeId ty : superUnion)
{
SubtypingResult next = isCovariantWith(env, subTy, ty, scope);
if (next.normalizationTooComplex)
return SubtypingResult{false, true};
if (next.isSubtype)
return FFlag::LuauUnifyWithSubtyping2 ? next : SubtypingResult{true};
if (FFlag::LuauMorePreciseErrorSuppression)
{
result.andAlso(next.withSuperComponent(TypePath::Index{index, TypePath::Index::Variant::Union}));
++index;
}
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull<Scope> scope)
{
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
result->isSubtype = true;
size_t i = 0;
for (TypeId ty : subUnion)
{
result->andAlso(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Union}));
if (result->normalizationTooComplex)
return SubtypingResult{false, true};
}
return *result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull<Scope> scope)
{
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
result->isSubtype = true;
size_t i = 0;
for (TypeId ty : superIntersection)
{
result->andAlso(isCovariantWith(env, subTy, ty, scope).withSuperComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (result->normalizationTooComplex)
return SubtypingResult{false, true};
}
return *result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull<Scope> scope)
{
std::unique_ptr<SubtypingResult> result = std::make_unique<SubtypingResult>();
result->isSubtype = false;
size_t i = 0;
for (TypeId ty : subIntersection)
{
result->orElse(isCovariantWith(env, ty, superTy, scope).withSubComponent(TypePath::Index{i++, TypePath::Index::Variant::Intersection}));
if (result->normalizationTooComplex)
return SubtypingResult{false, true};
}
return *result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull<Scope> scope)
{
TypeId negatedTy = follow(subNegation->ty);
SubtypingResult result;
if (is<NeverType>(negatedTy))
{
result = isCovariantWith(env, builtinTypes->unknownType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<UnknownType>(negatedTy))
{
result = isCovariantWith(env, builtinTypes->neverType, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (is<AnyType>(negatedTy))
{
result = isCovariantWith(env, negatedTy, superTy, scope).withSubComponent(TypePath::TypeField::Negated);
}
else if (auto u = get<UnionType>(negatedTy))
{
result = {true};
for (TypeId ty : u)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
result.andAlso(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
result.andAlso(isCovariantWith(env, &negatedTmp, superTy, scope));
}
}
}
else if (auto i = get<IntersectionType>(negatedTy))
{
result = {false};
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
result.orElse(isCovariantWith(env, negatedPart->ty, superTy, scope).withSubComponent(TypePath::TypeField::Negated));
else
{
NegationType negatedTmp{ty};
result.orElse(isCovariantWith(env, &negatedTmp, superTy, scope));
}
}
}
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
{
iceReporter->ice("attempting to negate a non-testable type");
}
else
{
result = SubtypingResult{false}.withSubComponent(TypePath::TypeField::Negated);
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull<Scope> scope)
{
TypeId negatedTy = follow(superNegation->ty);
SubtypingResult result;
if (is<NeverType>(negatedTy))
{
result = isCovariantWith(env, subTy, builtinTypes->unknownType, scope);
}
else if (is<UnknownType>(negatedTy))
{
result = isCovariantWith(env, subTy, builtinTypes->neverType, scope);
}
else if (is<AnyType>(negatedTy))
{
result = FFlag::LuauUnifyWithSubtyping2 ? isCovariantWith(env, subTy, negatedTy, scope) : isSubtype(subTy, negatedTy, scope);
}
else if (auto u = get<UnionType>(negatedTy))
{
std::vector<SubtypingResult> subtypings;
result = {true};
for (TypeId ty : u)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
result.andAlso(isCovariantWith(env, subTy, negatedPart->ty, scope));
else
{
NegationType negatedTmp{ty};
result.andAlso(isCovariantWith(env, subTy, &negatedTmp, scope));
}
}
}
else if (auto i = get<IntersectionType>(negatedTy))
{
result = {false};
for (TypeId ty : i)
{
if (auto negatedPart = get<NegationType>(follow(ty)))
result.orElse(isCovariantWith(env, subTy, negatedPart->ty, scope));
else
{
NegationType negatedTmp{ty};
result.orElse(isCovariantWith(env, subTy, &negatedTmp, scope));
}
}
}
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, negatedTy))
{
result = {p.first->type != p.second->type};
}
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, negatedTy))
{
if (get<StringSingleton>(p.first) && p.second->type == PrimitiveType::String)
result = {false};
else if (get<BooleanSingleton>(p.first) && p.second->type == PrimitiveType::Boolean)
result = {false};
else
result = {true};
}
else if (auto p = get2<PrimitiveType, SingletonType>(subTy, negatedTy))
{
if (p.first->type == PrimitiveType::String && get<StringSingleton>(p.second))
result = {false};
else if (p.first->type == PrimitiveType::Boolean && get<BooleanSingleton>(p.second))
result = {false};
else
result = {true};
}
else if (auto p = get2<ExternType, PrimitiveType>(subTy, negatedTy))
result = {true};
else if (auto p = get<PrimitiveType>(negatedTy); p && is<TableType, MetatableType>(subTy))
result = {p->type != PrimitiveType::Table};
else if (auto p = get2<FunctionType, PrimitiveType>(subTy, negatedTy))
result = {p.second->type != PrimitiveType::Function};
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
result = {*p.first != *p.second};
else if (auto p = get2<ExternType, ExternType>(subTy, negatedTy))
result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope));
else if (get2<FunctionType, ExternType>(subTy, negatedTy))
result = {true};
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
iceReporter->ice("attempting to negate a non-testable type");
else
result = {false};
return result.withSuperComponent(TypePath::TypeField::Negated);
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const PrimitiveType* subPrim,
const PrimitiveType* superPrim,
NotNull<Scope> scope
)
{
return {subPrim->type == superPrim->type};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const PrimitiveType* superPrim,
NotNull<Scope> scope
)
{
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
return {true};
else if (get<BooleanSingleton>(subSingleton) && superPrim->type == PrimitiveType::Boolean)
return {true};
else
return {false};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const SingletonType* superSingleton,
NotNull<Scope> scope
)
{
return {*subSingleton == *superSingleton};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TableType* subTable,
const TableType* superTable,
bool forceCovariantTest,
NotNull<Scope> scope
)
{
SubtypingResult result{true};
if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer)
{
return {false};
}
for (const auto& [name, superProp] : superTable->props)
{
std::vector<SubtypingResult> results;
if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
results.push_back(isCovariantWith(env, subIter->second, superProp, name, forceCovariantTest, scope));
else if (subTable->indexer)
{
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype)
{
if (superProp.isShared())
{
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
}
else
{
if (superProp.readTy)
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
if (superProp.writeTy)
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy, scope)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::write(name)));
}
}
}
else if (FFlag::LuauSubtypingMissingPropertiesAsNil)
{
SubtypingResult result = isCovariantWith(env, Property::readonly(builtinTypes->nilType), superProp, name, forceCovariantTest, scope);
result.reasoning.clear();
results.push_back(result);
}
if (results.empty())
return SubtypingResult{false};
if (FFlag::LuauMorePreciseErrorSuppression)
{
bool isSubtype = true;
for (const SubtypingResult& sr : results)
isSubtype &= sr.isSubtype;
if (result.isSubtype && !isSubtype)
{
for (const SubtypingResult& sr : results)
result.andAlso(sr, SubtypingSuppressionPolicy::Any);
}
else
{
for (const SubtypingResult& sr : results)
result.andAlso(sr, SubtypingSuppressionPolicy::All);
}
}
else
{
for (auto&& sr : results)
result.andAlso(sr);
}
}
if (superTable->indexer)
{
if (subTable->indexer)
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope));
else if (subTable->state != TableState::Sealed)
{
return {true};
}
else
return {false};
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope)
{
return isCovariantWith(env, subMt->table, superMt->table, scope)
.withBothComponent(TypePath::TypeField::Table)
.andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable, scope).withBothComponent(TypePath::TypeField::Metatable));
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope)
{
if (auto subTable = get<TableType>(follow(subMt->table)))
{
auto doDefault = [&]()
{
return isCovariantWith(env, subTable, superTable, false, scope);
};
auto subMTTable = get<TableType>(follow(subMt->metatable));
if (!subMTTable)
return doDefault();
auto indexProp = subMTTable->props.find("__index");
if (indexProp == subMTTable->props.end())
return doDefault();
if (!indexProp->second.readTy)
return doDefault();
auto indexTableProp = get<TableType>(follow(*indexProp->second.readTy));
if (!indexTableProp)
return doDefault();
TableType fauxSubTable{*subTable};
for (auto& [name, prop] : indexTableProp->props)
{
if (prop.readTy && fauxSubTable.props.find(name) == fauxSubTable.props.end())
fauxSubTable.props[name] = Property::readonly(*prop.readTy);
}
return isCovariantWith(env, &fauxSubTable, superTable, false, scope);
}
else
{
return {false};
}
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const MetatableType* subMt,
const PrimitiveType* superPrim,
NotNull<Scope> scope
)
{
LUAU_ASSERT(FFlag::LuauTableFreezeCheckIsSubtype);
if (superPrim->type == PrimitiveType::Table)
{
if (auto subTable = get<TableType>(follow(subMt->table)))
{
return isCovariantWith(env, subTable, superPrim, scope);
}
else if (auto subNestedMt = get<MetatableType>(follow(subMt->table)))
{
return isCovariantWith(env, subNestedMt, superPrim, scope);
}
}
return {false};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const ExternType* subExternType,
const ExternType* superExternType,
NotNull<Scope> scope
)
{
return {isSubclass(subExternType, superExternType)};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
TypeId subTy,
const ExternType* subExternType,
TypeId superTy,
const TableType* superTable,
NotNull<Scope> scope
)
{
SubtypingResult result{true};
env.substitutions[superTy] = subTy;
for (const auto& [name, prop] : superTable->props)
{
if (auto classProp = lookupExternTypeProp(subExternType, name))
{
result.andAlso(isCovariantWith(env, *classProp, prop, name, false, scope));
}
else
{
result = {false};
break;
}
}
if (superTable->indexer && subExternType->indexer)
{
result.andAlso(isCovariantWith(env, *subExternType->indexer, *superTable->indexer, scope));
}
else if (superTable->indexer && !subExternType->indexer)
{
result = { false};
}
env.substitutions[superTy] = nullptr;
return result;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const FunctionType* subFunction,
const FunctionType* superFunction,
NotNull<Scope> scope
)
{
SubtypingResult result;
if (!subFunction->generics.empty())
{
for (TypeId g : subFunction->generics)
{
g = follow(g);
if (get<GenericType>(g))
{
if (auto bounds = env.mappedGenerics.find(g))
bounds->emplace_back();
else
env.mappedGenerics[g] = {SubtypingEnvironment::GenericBounds{}};
}
}
}
if (!subFunction->genericPacks.empty())
{
std::vector<TypePackId> packs;
packs.reserve(subFunction->genericPacks.size());
for (TypePackId g : subFunction->genericPacks)
{
g = follow(g);
if (get<GenericTypePack>(g))
packs.emplace_back(g);
}
env.mappedGenericPacks.pushFrame(packs);
}
{
result.orElse(
isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments)
);
if (!result.isSubtype)
{
auto [arguments, tail] = flatten(superFunction->argTypes);
if (auto variadic = get<VariadicTypePack>(tail); variadic && variadic->hidden)
{
result.orElse(isContravariantWith(env, subFunction->argTypes, arena->addTypePack(TypePack{arguments}), scope)
.withBothComponent(TypePath::PackField::Arguments));
}
}
}
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes)
{
if (FFlag::LuauOverloadGetsInstantiated)
{
if (superFunction->generics.size() != subFunction->generics.size() && !superFunction->generics.empty())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
);
if (superFunction->genericPacks.size() != subFunction->genericPacks.size() && !superFunction->genericPacks.empty())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
);
}
else
{
if (superFunction->generics.size() != subFunction->generics.size())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
);
if (superFunction->genericPacks.size() != subFunction->genericPacks.size())
result.andAlso({false}).withError(
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
);
}
}
if (!subFunction->generics.empty())
{
for (TypeId g : subFunction->generics)
{
g = follow(g);
if (const GenericType* gen = get<GenericType>(g))
{
auto bounds = env.mappedGenerics.find(g);
LUAU_ASSERT(bounds && !bounds->empty());
result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name));
bounds->pop_back();
}
}
}
if (!subFunction->genericPacks.empty())
{
env.mappedGenericPacks.popFrame();
result.isCacheable = false;
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull<Scope> scope)
{
SubtypingResult result{false};
if (superPrim->type == PrimitiveType::Table)
result.isSubtype = true;
return result;
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull<Scope> scope)
{
SubtypingResult result{false};
if (subPrim->type == PrimitiveType::String)
{
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
{
if (auto mttv = get<TableType>(follow(metatable)))
{
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{
LUAU_ASSERT(*it->second.readTy);
if (auto stringTable = get<TableType>(*it->second.readTy))
result.orElse(isCovariantWith(env, stringTable, superTable, false, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
}
}
}
else if (subPrim->type == PrimitiveType::Table)
{
const bool isSubtype = superTable->props.empty() && (!superTable->indexer.has_value() || superTable->state == TableState::Generic);
return {isSubtype};
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const SingletonType* subSingleton,
const TableType* superTable,
NotNull<Scope> scope
)
{
SubtypingResult result{false};
if (get<StringSingleton>(subSingleton))
{
if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes))
{
if (auto mttv = get<TableType>(follow(metatable)))
{
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
{
LUAU_ASSERT(*it->second.readTy);
if (auto stringTable = get<TableType>(*it->second.readTy))
result.orElse(isCovariantWith(env, stringTable, superTable, false, scope)
.withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
}
}
}
}
return result;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TableIndexer& subIndexer,
const TableIndexer& superIndexer,
NotNull<Scope> scope
)
{
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType, scope)
.withBothComponent(TypePath::TypeField::IndexLookup)
.andAlso(
isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType, scope).withBothComponent(TypePath::TypeField::IndexResult)
);
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const Property& subProp,
const Property& superProp,
const std::string& name,
bool forceCovariantTest,
NotNull<Scope> scope
)
{
SubtypingResult res{true};
if (superProp.isShared() && subProp.isShared())
{
if (forceCovariantTest)
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
else
res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
}
else
{
if (superProp.readTy.has_value() && subProp.readTy.has_value())
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
if (superProp.writeTy.has_value() && subProp.writeTy.has_value() && !forceCovariantTest)
res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name)));
if (superProp.isReadWrite())
{
if (subProp.isReadOnly())
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::read(name)));
else if (subProp.isWriteOnly())
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::write(name)));
}
}
return res;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const std::shared_ptr<const NormalizedType>& subNorm,
const std::shared_ptr<const NormalizedType>& superNorm,
NotNull<Scope> scope
)
{
if (!subNorm || !superNorm)
return {false, true};
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso(isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope)
.orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)));
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings, scope));
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables, scope));
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads, scope));
result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers, scope));
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables, scope));
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions, scope));
return result;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const NormalizedExternType& superExternType,
NotNull<Scope> scope
)
{
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes)
{
result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope));
if (!result.isSubtype)
continue;
for (TypeId negation : superNegations)
{
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope)));
if (result.isSubtype)
break;
}
}
if (!result.isSubtype)
return result;
}
return {true};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
for (const auto& [subExternTypeTy, _] : subExternType.externTypes)
{
SubtypingResult result;
for (TypeId superTableTy : superTables)
result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope));
if (!result.isSubtype)
return result;
}
return {true};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const NormalizedStringType& superString,
NotNull<Scope> scope
)
{
bool isSubtype = Luau::isSubtype(subString, superString);
return {isSubtype};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedStringType& subString,
const TypeIds& superTables,
NotNull<Scope> scope
)
{
if (subString.isNever())
return {true};
if (subString.isCofinite)
{
SubtypingResult result;
for (const auto& superTable : superTables)
{
result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable, scope));
if (result.isSubtype)
return result;
}
return result;
}
for (const auto& superTable : superTables)
{
SubtypingResult result{true};
for (const auto& [_, subString] : subString.singletons)
{
result.andAlso(isCovariantWith(env, subString, superTable, scope));
if (!result.isSubtype)
break;
}
if (!result.isSubtype)
continue;
else
return result;
}
return {false};
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const NormalizedFunctionType& subFunction,
const NormalizedFunctionType& superFunction,
NotNull<Scope> scope
)
{
if (subFunction.isNever())
return {true};
else if (superFunction.isTop)
return {true};
else
return isCovariantWith(env, subFunction.parts, superFunction.parts, scope);
}
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull<Scope> scope)
{
auto result = std::make_unique<SubtypingResult>();
result->isSubtype = true;
for (TypeId subTy : subTypes)
{
auto innerResult = std::make_unique<SubtypingResult>();
for (TypeId superTy : superTypes)
{
innerResult->orElse(isCovariantWith(env, subTy, superTy, scope));
if (innerResult->normalizationTooComplex)
return SubtypingResult{false, true};
}
result->andAlso(*innerResult);
}
return *result;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const VariadicTypePack* subVariadic,
const VariadicTypePack* superVariadic,
NotNull<Scope> scope
)
{
return isCovariantWith(env, subVariadic->ty, superVariadic->ty, scope).withBothComponent(TypePath::TypeField::Variadic);
}
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) const
{
subTy = follow(subTy);
superTy = follow(superTy);
std::optional<SubtypingEnvironment::GenericBounds> originalSubTyBounds = std::nullopt;
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
{
LUAU_ASSERT(get<GenericType>(subTy));
originalSubTyBounds = SubtypingEnvironment::GenericBounds{subBounds->back()};
auto& [lowerSubBounds, upperSubBounds] = subBounds->back();
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
{
LUAU_ASSERT(get<GenericType>(superTy));
const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
maybeUpdateBounds(subTy, superTy, upperSubBounds, lowerSuperBounds, upperSuperBounds);
}
else
upperSubBounds.insert(superTy);
}
else if (env.containsMappedType(subTy))
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
{
LUAU_ASSERT(get<GenericType>(superTy));
auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
if (originalSubTyBounds)
{
LUAU_ASSERT(get<GenericType>(subTy));
const auto& [originalLowerSubBound, originalUpperSubBound] = *originalSubTyBounds;
maybeUpdateBounds(superTy, subTy, lowerSuperBounds, originalUpperSubBound, originalLowerSubBound);
}
else
lowerSuperBounds.insert(subTy);
}
else if (env.containsMappedType(superTy))
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
return true;
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TypeFunctionInstanceType* subFunctionInstance,
const TypeId superTy,
NotNull<Scope> scope
)
{
auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance, scope);
return isCovariantWith(env, ty, superTy, scope).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
}
SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const TypeId subTy,
const TypeFunctionInstanceType* superFunctionInstance,
NotNull<Scope> scope
)
{
auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance, scope);
return isCovariantWith(env, subTy, ty, scope).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
}
template<typename T, typename Container>
TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
{
if (container.empty())
return orElse;
else if (container.size() == 1)
return *begin(container);
else
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
}
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull<Scope> scope)
{
TypeFunctionContext context{arena, builtinTypes, scope, normalizer, typeFunctionRuntime, iceReporter, NotNull{&limits}};
TypeId function = arena->addType(*functionInstance);
FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, NotNull{&context}, true);
ErrorVec errors;
if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0)
{
errors.emplace_back(Location{}, UninhabitedTypeFunction{function});
return {builtinTypes->neverType, errors};
}
if (result.reducedTypes.contains(function))
return {function, errors};
return {builtinTypes->neverType, errors};
}
SubtypingResult Subtyping::trySemanticSubtyping(
SubtypingEnvironment& env,
TypeId subTy,
TypeId superTy,
NotNull<Scope> scope,
SubtypingResult& original
)
{
SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope);
if (semantic.normalizationTooComplex)
{
return semantic;
}
else if (semantic.isSubtype)
{
semantic.reasoning.clear();
return semantic;
}
return original;
}
SubtypingResult Subtyping::checkGenericBounds(
const SubtypingEnvironment::GenericBounds& bounds,
SubtypingEnvironment& env,
NotNull<Scope> scope,
std::string_view genericName
)
{
SubtypingResult result{true};
const auto& [lb, ub] = bounds;
if (FFlag::LuauSubtypingReplaceBounds)
{
UnionBuilder aggregateLowerBound{arena, builtinTypes};
aggregateLowerBound.reserve(lb.size());
for (TypeId t : lb)
{
if (const auto mappedBounds = env.mappedGenerics.find(t); mappedBounds && mappedBounds->empty())
continue;
aggregateLowerBound.add(t);
}
TypeId lowerBound = aggregateLowerBound.build();
IntersectionBuilder aggregateUpperBound{arena, builtinTypes};
aggregateUpperBound.reserve(ub.size());
for (TypeId t : ub)
{
if (const auto mappedBounds = env.mappedGenerics.find(t); mappedBounds && mappedBounds->empty())
continue;
aggregateUpperBound.add(t);
}
TypeId upperBound = aggregateUpperBound.build();
if (auto substLowerBound = env.applyMappedGenerics(builtinTypes, arena, lowerBound, iceReporter))
lowerBound = *substLowerBound;
if (auto substUpperBound = env.applyMappedGenerics(builtinTypes, arena, upperBound, iceReporter))
upperBound = *substUpperBound;
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
if (!nt || res == NormalizationResult::HitLimits)
result.normalizationTooComplex = true;
else if (res == NormalizationResult::False)
{
result.isSubtype = false;
}
SubtypingEnvironment boundsEnv;
boundsEnv.parent = &env;
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
boundsResult.reasoning.clear();
if (res == NormalizationResult::False)
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
else if (!boundsResult.isSubtype)
{
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
[[fallthrough]];
case ErrorSuppression::DoNotSuppress:
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
break;
default:
LUAU_ASSERT(0);
break;
}
}
result.andAlso(boundsResult);
}
else
{
TypeIds lbTypes;
for (TypeId t : lb)
{
t = follow(t);
if (const auto mappedBounds = env.mappedGenerics.find(t))
{
if (mappedBounds->empty())
continue;
auto& [lowerBound, upperBound] = mappedBounds->back();
if (!upperBound.empty())
lbTypes.insert(upperBound.begin(), upperBound.end());
else if (!lowerBound.empty())
lbTypes.insert(lowerBound.begin(), lowerBound.end());
else
lbTypes.insert(builtinTypes->unknownType);
}
else
lbTypes.insert(t);
}
TypeIds ubTypes;
for (TypeId t : ub)
{
t = follow(t);
if (const auto mappedBounds = env.mappedGenerics.find(t))
{
if (mappedBounds->empty())
continue;
auto& [lowerBound, upperBound] = mappedBounds->back();
if (!lowerBound.empty())
ubTypes.insert(lowerBound.begin(), lowerBound.end());
else if (!upperBound.empty())
ubTypes.insert(upperBound.begin(), upperBound.end());
else
ubTypes.insert(builtinTypes->unknownType);
}
else
ubTypes.insert(t);
}
TypeId lowerBound = makeAggregateType<UnionType>(lbTypes.take(), builtinTypes->neverType);
TypeId upperBound = makeAggregateType<IntersectionType>(ubTypes.take(), builtinTypes->unknownType);
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
if (!nt || res == NormalizationResult::HitLimits)
result.normalizationTooComplex = true;
else if (res == NormalizationResult::False)
{
result.isSubtype = false;
}
SubtypingEnvironment boundsEnv;
boundsEnv.parent = &env;
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
boundsResult.reasoning.clear();
if (res == NormalizationResult::False)
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
else if (!boundsResult.isSubtype)
{
switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound)))
{
case ErrorSuppression::Suppress:
break;
case ErrorSuppression::NormalizationFailed:
[[fallthrough]];
case ErrorSuppression::DoNotSuppress:
result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound);
break;
default:
LUAU_ASSERT(0);
break;
}
}
result.andAlso(boundsResult);
}
return result;
}
void Subtyping::maybeUpdateBounds(
TypeId here,
TypeId there,
TypeIds& boundsToUpdate,
const TypeIds& firstBoundsToCheck,
const TypeIds& secondBoundsToCheck
)
{
bool boundsChanged = false;
if (!firstBoundsToCheck.empty())
{
for (const TypeId t : firstBoundsToCheck)
{
if (t != here)
{
boundsToUpdate.insert(t);
boundsChanged = true;
}
}
}
if (!boundsChanged && !secondBoundsToCheck.empty())
{
for (const TypeId t : secondBoundsToCheck)
{
if (t != here)
{
boundsToUpdate.insert(t);
boundsChanged = true;
}
}
}
if (!boundsChanged && here != there)
boundsToUpdate.insert(there);
}
}