#include "Luau/Normalize.h"
#include "Luau/ToString.h"
#include <algorithm>
#include "Luau/Common.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Set.h"
#include "Luau/Simplify.h"
#include "Luau/Subtyping.h"
#include "Luau/Type.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h"
#include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauNormalizerInitialFuel, 3000)
LUAU_FASTFLAG(LuauIntegerType)
LUAU_FASTFLAGVARIABLE(LuauExternTypesNormalizeWithShapes)
namespace Luau
{
static bool shouldEarlyExit(NormalizationResult res)
{
if (res == NormalizationResult::HitLimits || res == NormalizationResult::False)
return true;
return false;
}
class NormalizerHitLimits : public std::exception
{
};
struct FuelInitializer
{
NotNull<Normalizer> normalizer;
bool initializedFuel;
explicit FuelInitializer(NotNull<Normalizer> normalizer)
: normalizer(normalizer)
, initializedFuel(normalizer->initializeFuel())
{
}
FuelInitializer(const FuelInitializer& rhs) = delete;
FuelInitializer& operator=(const FuelInitializer&) = delete;
~FuelInitializer()
{
if (initializedFuel)
normalizer->clearFuel();
}
};
NormalizedStringType::NormalizedStringType() {}
NormalizedStringType::NormalizedStringType(bool isCofinite, std::map<std::string, TypeId> singletons)
: isCofinite(isCofinite)
, singletons(std::move(singletons))
{
}
void NormalizedStringType::resetToString()
{
isCofinite = true;
singletons.clear();
}
void NormalizedStringType::resetToNever()
{
isCofinite = false;
singletons.clear();
}
bool NormalizedStringType::isNever() const
{
return !isCofinite && singletons.empty();
}
bool NormalizedStringType::isString() const
{
return isCofinite && singletons.empty();
}
bool NormalizedStringType::isUnion() const
{
return !isCofinite;
}
bool NormalizedStringType::isIntersection() const
{
return isCofinite;
}
bool NormalizedStringType::includes(const std::string& str) const
{
if (isString())
return true;
else if (isUnion() && singletons.count(str))
return true;
else if (isIntersection() && !singletons.count(str))
return true;
else
return false;
}
const NormalizedStringType NormalizedStringType::never;
bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr)
{
if (subStr.isUnion() && (superStr.isUnion() && !superStr.isNever()))
{
for (auto [name, ty] : subStr.singletons)
{
if (!superStr.singletons.count(name))
return false;
}
}
else if (subStr.isString() && superStr.isUnion())
return false;
return true;
}
void NormalizedExternType::pushPair(TypeId ty, TypeIds negations)
{
auto result = externTypes.insert(std::make_pair(ty, std::move(negations)));
if (result.second)
ordering.push_back(ty);
LUAU_ASSERT(ordering.size() == externTypes.size());
}
void NormalizedExternType::resetToNever()
{
ordering.clear();
externTypes.clear();
if (FFlag::LuauExternTypesNormalizeWithShapes)
shapeExtensions.clear();
}
bool NormalizedExternType::isNever() const
{
return externTypes.empty();
}
void NormalizedFunctionType::resetToTop()
{
isTop = true;
parts.clear();
}
void NormalizedFunctionType::resetToNever()
{
isTop = false;
parts.clear();
}
bool NormalizedFunctionType::isNever() const
{
return !isTop && parts.empty();
}
NormalizedType::NormalizedType(NotNull<BuiltinTypes> builtinTypes)
: builtinTypes(builtinTypes)
, tops(builtinTypes->neverType)
, booleans(builtinTypes->neverType)
, errors(builtinTypes->neverType)
, nils(builtinTypes->neverType)
, numbers(builtinTypes->neverType)
, integers(builtinTypes->neverType)
, strings{NormalizedStringType::never}
, threads(builtinTypes->neverType)
, buffers(builtinTypes->neverType)
{
}
bool NormalizedType::isUnknown() const
{
if (get<UnknownType>(tops))
return true;
bool hasAllPrimitives;
if (FFlag::LuauIntegerType)
{
hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && isThread(threads) && isBuffer(buffers) && isInteger(integers);
}
else
{
hasAllPrimitives = isPrim(booleans, PrimitiveType::Boolean) && isPrim(nils, PrimitiveType::NilType) && isNumber(numbers) &&
strings.isString() && isThread(threads) && isBuffer(buffers);
}
bool isTopExternType = false;
for (const auto& [t, disj] : externTypes.externTypes)
{
if (get<ExternType>(t))
{
if (t == builtinTypes->externType && disj.empty())
{
isTopExternType = true;
break;
}
}
}
bool isTopTable = false;
for (auto t : tables)
{
if (isPrim(t, PrimitiveType::Table))
{
isTopTable = true;
break;
}
}
return get<NeverType>(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop;
}
bool NormalizedType::isExactlyNumber() const
{
if (FFlag::LuauIntegerType)
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars() && !hasIntegers();
else
return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfString() const
{
if (FFlag::LuauIntegerType)
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars() && !hasIntegers();
else
return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::isSubtypeOfBooleans() const
{
if (FFlag::LuauIntegerType)
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars() && !hasIntegers();
else
return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars();
}
bool NormalizedType::shouldSuppressErrors() const
{
return hasErrors() || get<AnyType>(tops);
}
bool NormalizedType::hasTopTable() const
{
return hasTables() && std::any_of(
tables.begin(),
tables.end(),
[&](TypeId ty)
{
auto primTy = get<PrimitiveType>(ty);
return primTy && primTy->type == PrimitiveType::Type::Table;
}
);
}
bool NormalizedType::hasTops() const
{
return !get<NeverType>(tops);
}
bool NormalizedType::hasBooleans() const
{
return !get<NeverType>(booleans);
}
bool NormalizedType::hasExternTypes() const
{
return !externTypes.isNever();
}
bool NormalizedType::hasErrors() const
{
return !get<NeverType>(errors);
}
bool NormalizedType::hasNils() const
{
return !get<NeverType>(nils);
}
bool NormalizedType::hasNumbers() const
{
return !get<NeverType>(numbers);
}
bool NormalizedType::hasIntegers() const
{
if (FFlag::LuauIntegerType)
return get<NeverType>(integers) == nullptr;
else
return false;
}
bool NormalizedType::hasStrings() const
{
return !strings.isNever();
}
bool NormalizedType::hasThreads() const
{
return !get<NeverType>(threads);
}
bool NormalizedType::hasBuffers() const
{
return !get<NeverType>(buffers);
}
bool NormalizedType::hasTables() const
{
return !tables.isNever();
}
bool NormalizedType::hasFunctions() const
{
return !functions.isNever();
}
bool NormalizedType::hasTyvars() const
{
return !tyvars.empty();
}
bool NormalizedType::isFalsy() const
{
bool hasAFalse = false;
if (auto singleton = get<SingletonType>(booleans))
{
if (auto bs = singleton->variant.get_if<BooleanSingleton>())
hasAFalse = !bs->value;
}
if (FFlag::LuauIntegerType)
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars() && !hasIntegers());
else
return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() &&
!hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars());
}
bool NormalizedType::isTruthy() const
{
return !isFalsy();
}
bool NormalizedType::isNil() const
{
if (!hasNils())
return false;
if (FFlag::LuauIntegerType)
return !hasTops() && !hasBooleans() && !hasExternTypes() && !hasNumbers() && !hasStrings() && !hasThreads() && !hasBuffers() &&
!hasTables() && !hasFunctions() && !hasTyvars() && !hasIntegers();
else
return !hasTops() && !hasBooleans() && !hasExternTypes() && !hasNumbers() && !hasStrings() && !hasThreads() && !hasBuffers() &&
!hasTables() && !hasFunctions() && !hasTyvars();
}
static bool isShallowInhabited(const NormalizedType& norm)
{
if (FFlag::LuauIntegerType)
{
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
(get<NeverType>(norm.buffers) == nullptr) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty() ||
(get<NeverType>(norm.integers) == nullptr);
}
else
{
return !get<NeverType>(norm.tops) || !get<NeverType>(norm.booleans) || !norm.externTypes.isNever() || !get<NeverType>(norm.errors) ||
!get<NeverType>(norm.nils) || !get<NeverType>(norm.numbers) || !norm.strings.isNever() || !get<NeverType>(norm.threads) ||
!get<NeverType>(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
}
NormalizationResult Normalizer::isInhabited(const NormalizedType* norm)
{
Set<TypeId> seen{nullptr};
try
{
FuelInitializer fi{NotNull{this}};
return isInhabited(norm, seen);
}
catch (const NormalizerHitLimits&)
{
return NormalizationResult::HitLimits;
}
}
NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set<TypeId>& seen)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits() || !norm)
return NormalizationResult::HitLimits;
consumeFuel();
if (FFlag::LuauIntegerType)
{
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
!get<NeverType>(norm->integers) || !norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True;
}
else
{
if (!get<NeverType>(norm->tops) || !get<NeverType>(norm->booleans) || !get<NeverType>(norm->errors) || !get<NeverType>(norm->nils) ||
!get<NeverType>(norm->numbers) || !get<NeverType>(norm->threads) || !get<NeverType>(norm->buffers) || !norm->externTypes.isNever() ||
!norm->strings.isNever() || !norm->functions.isNever())
return NormalizationResult::True;
}
for (const auto& [_, intersect] : norm->tyvars)
{
NormalizationResult res = isInhabited(intersect.get(), seen);
if (res != NormalizationResult::False)
return res;
}
for (TypeId table : norm->tables)
{
NormalizationResult res = isInhabited(table, seen);
if (res != NormalizationResult::False)
return res;
}
return NormalizationResult::False;
}
NormalizationResult Normalizer::isInhabited(TypeId ty)
{
if (cacheInhabitance)
{
if (bool* result = cachedIsInhabited.find(ty))
return *result ? NormalizationResult::True : NormalizationResult::False;
}
Set<TypeId> seen{nullptr};
try
{
FuelInitializer fi{NotNull{this}};
NormalizationResult result = isInhabited(ty, seen);
if (cacheInhabitance && result == NormalizationResult::True)
cachedIsInhabited[ty] = true;
else if (cacheInhabitance && result == NormalizationResult::False)
cachedIsInhabited[ty] = false;
return result;
}
catch (const NormalizerHitLimits&)
{
return NormalizationResult::HitLimits;
}
}
NormalizationResult Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
consumeFuel();
ty = follow(ty);
if (get<NeverType>(ty))
return NormalizationResult::False;
if (!get<IntersectionType>(ty) && !get<UnionType>(ty) && !get<TableType>(ty) && !get<MetatableType>(ty))
return NormalizationResult::True;
if (seen.count(ty))
return NormalizationResult::True;
seen.insert(ty);
if (const TableType* ttv = get<TableType>(ty))
{
for (const auto& [_, prop] : ttv->props)
{
if (useNewLuauSolver())
{
if (auto ty = prop.readTy)
{
NormalizationResult res = isInhabited(*ty, seen);
if (res != NormalizationResult::True)
return res;
}
}
else
{
NormalizationResult res = isInhabited(prop.type_DEPRECATED(), seen);
if (res != NormalizationResult::True)
return res;
}
}
return NormalizationResult::True;
}
if (const MetatableType* mtv = get<MetatableType>(ty))
{
NormalizationResult res = isInhabited(mtv->table, seen);
if (res != NormalizationResult::True)
return res;
return isInhabited(mtv->metatable, seen);
}
std::shared_ptr<const NormalizedType> norm = normalize(ty);
return isInhabited(norm.get(), seen);
}
NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId right)
{
Set<TypeId> seen{nullptr};
SeenTablePropPairs seenTablePropPairs{{nullptr, nullptr}};
try
{
FuelInitializer fi{NotNull{this}};
return isIntersectionInhabited(left, right, seenTablePropPairs, seen);
}
catch (const NormalizerHitLimits&)
{
return NormalizationResult::HitLimits;
}
}
NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId right, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet)
{
consumeFuel();
left = follow(left);
right = follow(right);
if (cacheInhabitance)
{
if (bool* result = cachedIsInhabitedIntersection.find({left, right}))
return *result ? NormalizationResult::True : NormalizationResult::False;
}
NormalizedType norm{builtinTypes};
NormalizationResult res = normalizeIntersections({left, right}, norm, seenTablePropPairs, seenSet);
if (res != NormalizationResult::True)
{
if (cacheInhabitance && res == NormalizationResult::False)
cachedIsInhabitedIntersection[{left, right}] = false;
return res;
}
NormalizationResult result = isInhabited(&norm, seenSet);
if (cacheInhabitance && result == NormalizationResult::True)
cachedIsInhabitedIntersection[{left, right}] = true;
else if (cacheInhabitance && result == NormalizationResult::False)
cachedIsInhabitedIntersection[{left, right}] = false;
return result;
}
static int tyvarIndex(TypeId ty)
{
if (const GenericType* gtv = get<GenericType>(ty))
return gtv->index;
else if (const FreeType* ftv = get<FreeType>(ty))
return ftv->index;
else if (const BlockedType* btv = get<BlockedType>(ty))
return btv->index;
else
return 0;
}
static bool isTop(NotNull<BuiltinTypes> builtinTypes, const NormalizedExternType& externTypes)
{
if (externTypes.externTypes.size() != 1)
return false;
auto first = externTypes.externTypes.begin();
if (first->first != builtinTypes->externType)
return false;
if (!first->second.empty())
return false;
return true;
}
static void resetToTop(NotNull<BuiltinTypes> builtinTypes, NormalizedExternType& externTypes)
{
externTypes.ordering.clear();
externTypes.externTypes.clear();
externTypes.pushPair(builtinTypes->externType, TypeIds{});
}
#ifdef LUAU_ASSERTENABLED
static bool isNormalizedTop(TypeId ty)
{
return get<NeverType>(ty) || get<AnyType>(ty) || get<UnknownType>(ty);
}
static bool isNormalizedBoolean(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::Boolean;
else if (const SingletonType* stv = get<SingletonType>(ty))
return get<BooleanSingleton>(stv);
else
return false;
}
static bool isNormalizedError(TypeId ty)
{
if (get<NeverType>(ty) || get<ErrorType>(ty))
return true;
else
return false;
}
static bool isNormalizedNil(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::NilType;
else
return false;
}
static bool isNormalizedNumber(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::Number;
else
return false;
}
static bool isNormalizedInteger(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::Integer;
else
return false;
}
static bool isNormalizedString(const NormalizedStringType& ty)
{
if (ty.isString())
return true;
for (auto& [str, ty] : ty.singletons)
{
if (const SingletonType* stv = get<SingletonType>(ty))
{
if (const StringSingleton* sstv = get<StringSingleton>(stv))
{
if (sstv->value != str)
return false;
}
else
return false;
}
else
return false;
}
return true;
}
static bool isNormalizedThread(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::Thread;
else
return false;
}
static bool isNormalizedBuffer(TypeId ty)
{
if (get<NeverType>(ty))
return true;
else if (const PrimitiveType* ptv = get<PrimitiveType>(ty))
return ptv->type == PrimitiveType::Buffer;
else
return false;
}
static bool areNormalizedFunctions(const NormalizedFunctionType& tys)
{
for (TypeId ty : tys.parts)
{
if (!get<FunctionType>(ty) && !get<ErrorType>(ty))
return false;
}
return true;
}
static bool areNormalizedTables(const TypeIds& tys)
{
for (TypeId ty : tys)
{
if (get<TableType>(ty) || get<MetatableType>(ty))
continue;
const PrimitiveType* pt = get<PrimitiveType>(ty);
if (!pt)
return false;
if (pt->type == PrimitiveType::Table)
continue;
return false;
}
return true;
}
static bool areNormalizedExternTypes(const NormalizedExternType& tys)
{
for (const auto& [ty, negations] : tys.externTypes)
{
const ExternType* etv = get<ExternType>(ty);
if (!etv)
{
return false;
}
for (TypeId negation : negations)
{
const ExternType* nctv = get<ExternType>(negation);
if (!nctv)
{
return false;
}
if (!isSubclass(nctv, etv))
{
return false;
}
}
for (const auto& [otherTy, otherNegations] : tys.externTypes)
{
if (otherTy == ty)
continue;
const ExternType* octv = get<ExternType>(otherTy);
if (!octv)
{
return false;
}
if (isSubclass(etv, octv))
{
auto iss = [etv](TypeId t)
{
const ExternType* c = get<ExternType>(t);
if (!c)
return false;
return isSubclass(etv, c);
};
if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss))
return false;
}
}
}
return true;
}
static bool isPlainTyvar(TypeId ty)
{
return (get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty) || get<TypeFunctionInstanceType>(ty));
}
static bool isNormalizedTyvar(const NormalizedTyvars& tyvars)
{
for (auto& [tyvar, intersect] : tyvars)
{
if (!isPlainTyvar(tyvar))
return false;
if (!isShallowInhabited(*intersect))
return false;
for (auto& [other, _] : intersect->tyvars)
if (tyvarIndex(other) <= tyvarIndex(tyvar))
return false;
}
return true;
}
#endif
static void assertInvariant(const NormalizedType& norm)
{
#ifdef LUAU_ASSERTENABLED
if (!FFlag::DebugLuauCheckNormalizeInvariant)
return;
LUAU_ASSERT(isNormalizedTop(norm.tops));
LUAU_ASSERT(isNormalizedBoolean(norm.booleans));
LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes));
LUAU_ASSERT(isNormalizedError(norm.errors));
LUAU_ASSERT(isNormalizedNil(norm.nils));
LUAU_ASSERT(isNormalizedNumber(norm.numbers));
if (FFlag::LuauIntegerType)
LUAU_ASSERT(isNormalizedInteger(norm.integers));
LUAU_ASSERT(isNormalizedString(norm.strings));
LUAU_ASSERT(isNormalizedThread(norm.threads));
LUAU_ASSERT(isNormalizedBuffer(norm.buffers));
LUAU_ASSERT(areNormalizedFunctions(norm.functions));
LUAU_ASSERT(areNormalizedTables(norm.tables));
LUAU_ASSERT(isNormalizedTyvar(norm.tyvars));
for (auto& [_, child] : norm.tyvars)
assertInvariant(*child);
#endif
}
Normalizer::Normalizer(
TypeArena* arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<UnifierSharedState> sharedState,
SolverMode solverMode,
bool cacheInhabitance
)
: arena(arena)
, builtinTypes(builtinTypes)
, sharedState(sharedState)
, cacheInhabitance(cacheInhabitance)
, solverMode(solverMode)
{
}
static bool isCacheable(TypeId ty, Set<TypeId>& seen);
static bool isCacheable(TypePackId tp, Set<TypeId>& seen)
{
tp = follow(tp);
auto it = begin(tp);
auto endIt = end(tp);
for (; it != endIt; ++it)
{
if (!isCacheable(*it, seen))
return false;
}
if (auto tail = it.tail())
{
if (get<FreeTypePack>(*tail) || get<BlockedTypePack>(*tail) || get<TypeFunctionInstanceTypePack>(*tail))
return false;
}
return true;
}
static bool isCacheable(TypeId ty, Set<TypeId>& seen)
{
if (seen.contains(ty))
return true;
seen.insert(ty);
ty = follow(ty);
if (get<FreeType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty))
return false;
if (auto tfi = get<TypeFunctionInstanceType>(ty))
{
for (TypeId t : tfi->typeArguments)
{
if (!isCacheable(t, seen))
return false;
}
for (TypePackId tp : tfi->packArguments)
{
if (!isCacheable(tp, seen))
return false;
}
}
return true;
}
static bool isCacheable(TypeId ty)
{
Set<TypeId> seen{nullptr};
return isCacheable(ty, seen);
}
std::shared_ptr<const NormalizedType> Normalizer::normalize(TypeId ty)
{
if (!arena)
sharedState->iceHandler->ice("Normalizing types outside a module");
auto found = cachedNormals.find(ty);
if (found != cachedNormals.end())
return found->second;
NormalizedType norm{builtinTypes};
Set<TypeId> seenSetTypes{nullptr};
SeenTablePropPairs seenTablePropPairs{{nullptr, nullptr}};
try
{
FuelInitializer fi{NotNull{this}};
NormalizationResult res = unionNormalWithTy(norm, ty, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
return nullptr;
}
catch (const NormalizerHitLimits&)
{
return nullptr;
}
if (norm.isUnknown())
{
clearNormal(norm);
norm.tops = builtinTypes->unknownType;
}
std::shared_ptr<NormalizedType> shared = std::make_shared<NormalizedType>(std::move(norm));
if (shared->isCacheable)
cachedNormals[ty] = shared;
return shared;
}
NormalizationResult Normalizer::normalizeIntersections(
const std::vector<TypeId>& intersections,
NormalizedType& outType,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSet
)
{
if (!arena)
sharedState->iceHandler->ice("Normalizing types outside a module");
consumeFuel();
NormalizedType norm{builtinTypes};
norm.tops = builtinTypes->unknownType;
for (auto ty : intersections)
{
NormalizationResult res = intersectNormalWithTy(norm, ty, seenTablePropPairs, seenSet);
if (res != NormalizationResult::True)
return res;
}
NormalizationResult res = unionNormals(outType, norm);
if (res != NormalizationResult::True)
return res;
return NormalizationResult::True;
}
void Normalizer::clearNormal(NormalizedType& norm)
{
norm.tops = builtinTypes->neverType;
norm.booleans = builtinTypes->neverType;
norm.externTypes.resetToNever();
norm.errors = builtinTypes->neverType;
norm.nils = builtinTypes->neverType;
norm.numbers = builtinTypes->neverType;
norm.integers = builtinTypes->neverType;
norm.strings.resetToNever();
norm.threads = builtinTypes->neverType;
norm.buffers = builtinTypes->neverType;
norm.tables.clear();
norm.functions.resetToNever();
norm.tyvars.clear();
}
const TypeIds* Normalizer::cacheTypeIds(TypeIds tys)
{
auto found = cachedTypeIds.find(&tys);
if (found != cachedTypeIds.end())
return found->first;
std::unique_ptr<TypeIds> uniq = std::make_unique<TypeIds>(std::move(tys));
const TypeIds* result = uniq.get();
cachedTypeIds[result] = std::move(uniq);
return result;
}
TypeId Normalizer::unionType(TypeId here, TypeId there)
{
consumeFuel();
here = follow(here);
there = follow(there);
if (here == there)
return here;
if (get<NeverType>(here) || get<AnyType>(there))
return there;
if (get<NeverType>(there) || get<AnyType>(here))
return here;
TypeIds tmps;
if (const UnionType* utv = get<UnionType>(here))
{
TypeIds heres;
heres.insert(begin(utv), end(utv));
tmps.insert(heres.begin(), heres.end());
cachedUnions[cacheTypeIds(std::move(heres))] = here;
}
else
tmps.insert(here);
if (const UnionType* utv = get<UnionType>(there))
{
TypeIds theres;
theres.insert(begin(utv), end(utv));
tmps.insert(theres.begin(), theres.end());
cachedUnions[cacheTypeIds(std::move(theres))] = there;
}
else
tmps.insert(there);
auto cacheHit = cachedUnions.find(&tmps);
if (cacheHit != cachedUnions.end())
return cacheHit->second;
std::vector<TypeId> parts;
parts.insert(parts.end(), tmps.begin(), tmps.end());
TypeId result = arena->addType(UnionType{std::move(parts)});
cachedUnions[cacheTypeIds(std::move(tmps))] = result;
return result;
}
TypeId Normalizer::intersectionType(TypeId here, TypeId there)
{
consumeFuel();
here = follow(here);
there = follow(there);
if (here == there)
return here;
if (get<NeverType>(here) || get<AnyType>(there))
return here;
if (get<NeverType>(there) || get<AnyType>(here))
return there;
TypeIds tmps;
if (const IntersectionType* utv = get<IntersectionType>(here))
{
TypeIds heres;
heres.insert(begin(utv), end(utv));
tmps.insert(heres.begin(), heres.end());
cachedIntersections[cacheTypeIds(std::move(heres))] = here;
}
else
tmps.insert(here);
if (const IntersectionType* utv = get<IntersectionType>(there))
{
TypeIds theres;
theres.insert(begin(utv), end(utv));
tmps.insert(theres.begin(), theres.end());
cachedIntersections[cacheTypeIds(std::move(theres))] = there;
}
else
tmps.insert(there);
if (tmps.size() == 1)
return *tmps.begin();
auto cacheHit = cachedIntersections.find(&tmps);
if (cacheHit != cachedIntersections.end())
return cacheHit->second;
std::vector<TypeId> parts;
parts.insert(parts.end(), tmps.begin(), tmps.end());
TypeId result = arena->addType(IntersectionType{std::move(parts)});
cachedIntersections[cacheTypeIds(std::move(tmps))] = result;
return result;
}
void Normalizer::clearCaches()
{
cachedNormals.clear();
cachedIntersections.clear();
cachedUnions.clear();
cachedTypeIds.clear();
}
TypeId Normalizer::unionOfTops(TypeId here, TypeId there)
{
consumeFuel();
if (get<NeverType>(here) || get<AnyType>(there))
return there;
else
return here;
}
TypeId Normalizer::unionOfBools(TypeId here, TypeId there)
{
consumeFuel();
if (get<NeverType>(here))
return there;
if (get<NeverType>(there))
return here;
if (const BooleanSingleton* hbool = get<BooleanSingleton>(get<SingletonType>(here)))
if (const BooleanSingleton* tbool = get<BooleanSingleton>(get<SingletonType>(there)))
if (hbool->value == tbool->value)
return here;
return builtinTypes->booleanType;
}
void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there)
{
consumeFuel();
if (heres.count(there))
return;
const ExternType* tctv = get<ExternType>(there);
for (auto it = heres.begin(); it != heres.end();)
{
TypeId here = *it;
const ExternType* hctv = get<ExternType>(here);
if (isSubclass(tctv, hctv))
return;
else if (isSubclass(hctv, tctv))
it = heres.erase(it);
else
it++;
}
heres.insert(there);
}
void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres)
{
consumeFuel();
for (TypeId there : theres)
unionExternTypesWithExternType(heres, there);
}
static bool isSubclass(TypeId test, TypeId parent)
{
const ExternType* testCtv = get<ExternType>(test);
const ExternType* parentCtv = get<ExternType>(parent);
LUAU_ASSERT(testCtv);
LUAU_ASSERT(parentCtv);
return isSubclass(testCtv, parentCtv);
}
void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
consumeFuel();
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(there, hereTy))
{
for (auto nIt = hereNegations.begin(); nIt != hereNegations.end();)
{
TypeId hereNegation = *nIt;
if (isSubclass(there, hereNegation))
{
heres.pushPair(there, TypeIds{});
return;
}
else if (isSubclass(hereNegation, there))
{
nIt = hereNegations.erase(nIt);
}
else
{
++nIt;
}
}
return;
}
else if (isSubclass(hereTy, there))
{
TypeIds negations = std::move(hereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.pushPair(there, std::move(negations));
return;
}
++it;
}
heres.pushPair(there, TypeIds{});
}
void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
consumeFuel();
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
bool insert = true;
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
bool inserted = false;
for (auto nIt = hereNegations.begin(); nIt != hereNegations.end();)
{
TypeId hereNegateTy = *nIt;
if (isSubclass(thereTy, hereNegateTy))
{
inserted = true;
heres.pushPair(thereTy, thereNegations);
break;
}
else if (isSubclass(hereNegateTy, thereTy))
{
inserted = true;
nIt = hereNegations.erase(nIt);
break;
}
else
{
++nIt;
}
}
if (inserted)
{
insert = false;
break;
}
}
else if (isSubclass(hereTy, thereTy))
{
TypeIds negations = std::move(hereNegations);
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
insert = false;
break;
}
else if (hereTy == thereTy)
{
unionExternTypes(hereNegations, thereNegations);
insert = false;
break;
}
++it;
}
if (insert)
{
heres.pushPair(thereTy, thereNegations);
if (FFlag::LuauExternTypesNormalizeWithShapes)
{
for (TypeId shape : theres.shapeExtensions)
heres.shapeExtensions.insert(shape);
}
}
}
}
void Normalizer::unionStrings(NormalizedStringType& here, const NormalizedStringType& there)
{
consumeFuel();
if (there.isString())
here.resetToString();
else if (here.isUnion() && there.isUnion())
here.singletons.insert(there.singletons.begin(), there.singletons.end());
else if (here.isUnion() && there.isIntersection())
{
here.isCofinite = true;
for (const auto& pair : there.singletons)
{
auto it = here.singletons.find(pair.first);
if (it != end(here.singletons))
here.singletons.erase(it);
else
here.singletons.insert(pair);
}
}
else if (here.isIntersection() && there.isUnion())
{
for (const auto& [name, ty] : there.singletons)
here.singletons.erase(name);
}
else if (here.isIntersection() && there.isIntersection())
{
auto iter = begin(here.singletons);
auto endIter = end(here.singletons);
while (iter != endIter)
{
if (!there.singletons.count(iter->first))
{
auto eraseIt = iter;
++iter;
here.singletons.erase(eraseIt);
}
else
++iter;
}
}
else
LUAU_ASSERT(!"Unreachable");
}
std::optional<TypePackId> Normalizer::unionOfTypePacks(TypePackId here, TypePackId there)
{
consumeFuel();
if (here == there)
return here;
std::vector<TypeId> head;
std::optional<TypePackId> tail;
bool hereSubThere = true;
bool thereSubHere = true;
TypePackIterator ith = begin(here);
TypePackIterator itt = begin(there);
while (ith != end(here) && itt != end(there))
{
TypeId hty = *ith;
TypeId tty = *itt;
TypeId ty = unionType(hty, tty);
if (ty != hty)
thereSubHere = false;
if (ty != tty)
hereSubThere = false;
head.push_back(ty);
ith++;
itt++;
}
auto dealWithDifferentArities =
[&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere)
{
if (ith != end(here))
{
TypeId tty = builtinTypes->nilType;
if (std::optional<TypePackId> ttail = itt.tail())
{
if (const VariadicTypePack* tvtp = get<VariadicTypePack>(*ttail))
tty = tvtp->ty;
else
return false;
}
else
return false;
while (ith != end(here))
{
TypeId hty = *ith;
TypeId ty = unionType(hty, tty);
if (ty != hty)
thereSubHere = false;
if (ty != tty)
hereSubThere = false;
head.push_back(ty);
ith++;
}
}
return true;
};
if (!dealWithDifferentArities(ith, itt, here, there, hereSubThere, thereSubHere))
return std::nullopt;
if (!dealWithDifferentArities(itt, ith, there, here, thereSubHere, hereSubThere))
return std::nullopt;
if (std::optional<TypePackId> htail = ith.tail())
{
if (std::optional<TypePackId> ttail = itt.tail())
{
if (*htail == *ttail)
tail = htail;
else if (const VariadicTypePack* hvtp = get<VariadicTypePack>(*htail))
{
if (const VariadicTypePack* tvtp = get<VariadicTypePack>(*ttail))
{
TypeId ty = unionType(hvtp->ty, tvtp->ty);
if (ty != hvtp->ty)
thereSubHere = false;
if (ty != tvtp->ty)
hereSubThere = false;
bool hidden = hvtp->hidden & tvtp->hidden;
tail = arena->addTypePack(VariadicTypePack{ty, hidden});
}
else
return std::nullopt;
}
else
return std::nullopt;
}
else if (get<VariadicTypePack>(*htail))
{
hereSubThere = false;
tail = htail;
}
else
return std::nullopt;
}
else if (std::optional<TypePackId> ttail = itt.tail())
{
if (get<VariadicTypePack>(*ttail))
{
thereSubHere = false;
tail = htail;
}
else
return std::nullopt;
}
if (hereSubThere)
return there;
else if (thereSubHere)
return here;
if (!head.empty())
return arena->addTypePack(TypePack{std::move(head), tail});
else if (tail)
return *tail;
else
return arena->addTypePack({});
}
std::optional<TypeId> Normalizer::unionOfFunctions(TypeId here, TypeId there)
{
consumeFuel();
if (get<ErrorType>(here))
return here;
if (get<ErrorType>(there))
return there;
const FunctionType* hftv = get<FunctionType>(here);
LUAU_ASSERT(hftv);
const FunctionType* tftv = get<FunctionType>(there);
LUAU_ASSERT(tftv);
if (hftv->generics != tftv->generics)
return std::nullopt;
if (hftv->genericPacks != tftv->genericPacks)
return std::nullopt;
std::optional<TypePackId> argTypes = intersectionOfTypePacks_INTERNAL(hftv->argTypes, tftv->argTypes);
if (!argTypes)
return std::nullopt;
std::optional<TypePackId> retTypes = unionOfTypePacks(hftv->retTypes, tftv->retTypes);
if (!retTypes)
return std::nullopt;
if (*argTypes == hftv->argTypes && *retTypes == hftv->retTypes)
return here;
if (*argTypes == tftv->argTypes && *retTypes == tftv->retTypes)
return there;
FunctionType result{*argTypes, *retTypes};
result.generics = hftv->generics;
result.genericPacks = hftv->genericPacks;
return arena->addType(std::move(result));
}
void Normalizer::unionFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres)
{
consumeFuel();
if (heres.isTop)
return;
if (theres.isTop)
heres.resetToTop();
if (theres.isNever())
return;
TypeIds tmps;
if (heres.isNever())
{
tmps.insert(theres.parts.begin(), theres.parts.end());
heres.parts = std::move(tmps);
return;
}
for (TypeId here : heres.parts)
for (TypeId there : theres.parts)
{
if (std::optional<TypeId> fun = unionOfFunctions(here, there))
tmps.insert(*fun);
else
tmps.insert(builtinTypes->errorRecoveryType(there));
}
heres.parts = std::move(tmps);
}
void Normalizer::unionFunctionsWithFunction(NormalizedFunctionType& heres, TypeId there)
{
consumeFuel();
if (heres.isNever())
{
TypeIds tmps;
tmps.insert(there);
heres.parts = std::move(tmps);
return;
}
TypeIds tmps;
for (TypeId here : heres.parts)
{
if (std::optional<TypeId> fun = unionOfFunctions(here, there))
tmps.insert(*fun);
else
tmps.insert(builtinTypes->errorRecoveryType(there));
}
heres.parts = std::move(tmps);
}
void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there)
{
if (get<NeverType>(there))
return;
heres.insert(there);
}
void Normalizer::unionTables(TypeIds& heres, const TypeIds& theres)
{
consumeFuel();
for (TypeId there : theres)
{
if (there == builtinTypes->tableType)
{
heres.clear();
heres.insert(there);
return;
}
else
{
unionTablesWithTable(heres, there);
}
}
}
NormalizationResult Normalizer::unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{
consumeFuel();
here.isCacheable &= there.isCacheable;
TypeId tops = unionOfTops(here.tops, there.tops);
if (get<UnknownType>(tops) && (get<ErrorType>(here.errors) || get<ErrorType>(there.errors)))
tops = builtinTypes->anyType;
if (!get<NeverType>(tops))
{
clearNormal(here);
here.tops = tops;
return NormalizationResult::True;
}
for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++)
{
TypeId tyvar = it->first;
const NormalizedType& inter = *it->second;
int index = tyvarIndex(tyvar);
if (index <= ignoreSmallerTyvars)
continue;
auto [emplaced, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes}));
if (fresh)
{
NormalizationResult res = unionNormals(*emplaced->second, here, index);
if (res != NormalizationResult::True)
return res;
}
NormalizationResult res = unionNormals(*emplaced->second, inter, index);
if (res != NormalizationResult::True)
return res;
}
here.booleans = unionOfBools(here.booleans, there.booleans);
unionExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? here.errors : there.errors);
here.nils = (get<NeverType>(there.nils) ? here.nils : there.nils);
here.numbers = (get<NeverType>(there.numbers) ? here.numbers : there.numbers);
if (FFlag::LuauIntegerType)
here.integers = (get<NeverType>(there.integers) ? here.integers : there.integers);
unionStrings(here.strings, there.strings);
here.threads = (get<NeverType>(there.threads) ? here.threads : there.threads);
here.buffers = (get<NeverType>(there.buffers) ? here.buffers : there.buffers);
unionFunctions(here.functions, there.functions);
unionTables(here.tables, there.tables);
return NormalizationResult::True;
}
bool Normalizer::withinResourceLimits()
{
if (FInt::LuauNormalizeCacheLimit > 0)
{
size_t cacheUsage = cachedNormals.size() + cachedIntersections.size() + cachedUnions.size() + cachedTypeIds.size() +
cachedIsInhabited.size() + cachedIsInhabitedIntersection.size();
if (cacheUsage > size_t(FInt::LuauNormalizeCacheLimit))
{
clearCaches();
return false;
}
}
if (sharedState->counters.recursionLimit > 0)
if (sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
return false;
return true;
}
bool Normalizer::useNewLuauSolver() const
{
return solverMode == SolverMode::New;
}
NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect)
{
consumeFuel();
std::optional<NormalizedType> negated;
std::shared_ptr<const NormalizedType> normal = normalize(toNegate);
negated = negateNormal(*normal);
if (!negated)
return NormalizationResult::False;
intersectNormals(intersect, *negated);
return NormalizationResult::True;
}
NormalizationResult Normalizer::unionNormalWithTy(
NormalizedType& here,
TypeId there,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSetTypes,
int ignoreSmallerTyvars
)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
consumeFuel();
there = follow(there);
if (get<AnyType>(there) || get<UnknownType>(there))
{
TypeId tops = unionOfTops(here.tops, there);
if (get<UnknownType>(tops) && get<ErrorType>(here.errors))
tops = builtinTypes->anyType;
clearNormal(here);
here.tops = tops;
return NormalizationResult::True;
}
else if (get<NeverType>(there) || get<AnyType>(here.tops))
return NormalizationResult::True;
else if (get<ErrorType>(there) && get<UnknownType>(here.tops))
{
here.tops = builtinTypes->anyType;
return NormalizationResult::True;
}
else if (const UnionType* utv = get<UnionType>(there))
{
if (seenSetTypes.count(there))
return NormalizationResult::True;
seenSetTypes.insert(there);
for (UnionTypeIterator it = begin(utv); it != end(utv); ++it)
{
NormalizationResult res = unionNormalWithTy(here, *it, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
{
seenSetTypes.erase(there);
return res;
}
}
seenSetTypes.erase(there);
return NormalizationResult::True;
}
else if (const IntersectionType* itv = get<IntersectionType>(there))
{
if (seenSetTypes.count(there))
return NormalizationResult::True;
seenSetTypes.insert(there);
NormalizedType norm{builtinTypes};
norm.tops = builtinTypes->unknownType;
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
{
NormalizationResult res = intersectNormalWithTy(norm, *it, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
{
seenSetTypes.erase(there);
return res;
}
}
seenSetTypes.erase(there);
return unionNormals(here, norm);
}
else if (get<UnknownType>(here.tops))
return NormalizationResult::True;
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{
if (tyvarIndex(there) <= ignoreSmallerTyvars)
return NormalizationResult::True;
NormalizedType inter{builtinTypes};
inter.tops = builtinTypes->unknownType;
here.tyvars.insert_or_assign(there, std::make_unique<NormalizedType>(std::move(inter)));
if (!isCacheable(there))
here.isCacheable = false;
}
else if (get<FunctionType>(there))
unionFunctionsWithFunction(here.functions, there);
else if (get<TableType>(there) || get<MetatableType>(there))
unionTablesWithTable(here.tables, there);
else if (get<ExternType>(there))
unionExternTypesWithExternType(here.externTypes, there);
else if (get<ErrorType>(there))
here.errors = there;
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{
if (ptv->type == PrimitiveType::Boolean)
here.booleans = there;
else if (ptv->type == PrimitiveType::NilType)
here.nils = there;
else if (ptv->type == PrimitiveType::Number)
here.numbers = there;
else if (FFlag::LuauIntegerType && (ptv->type == PrimitiveType::Integer))
here.integers = there;
else if (ptv->type == PrimitiveType::String)
here.strings.resetToString();
else if (ptv->type == PrimitiveType::Thread)
here.threads = there;
else if (ptv->type == PrimitiveType::Buffer)
here.buffers = there;
else if (ptv->type == PrimitiveType::Function)
{
here.functions.resetToTop();
}
else if (ptv->type == PrimitiveType::Table)
{
here.tables.clear();
here.tables.insert(there);
}
else
LUAU_ASSERT(!"Unreachable");
}
else if (const SingletonType* stv = get<SingletonType>(there))
{
if (get<BooleanSingleton>(stv))
here.booleans = unionOfBools(here.booleans, there);
else if (const StringSingleton* sstv = get<StringSingleton>(stv))
{
if (here.strings.isCofinite)
{
auto it = here.strings.singletons.find(sstv->value);
if (it != here.strings.singletons.end())
here.strings.singletons.erase(it);
}
else
here.strings.singletons.insert({sstv->value, there});
}
else
LUAU_ASSERT(!"Unreachable");
}
else if (const NegationType* ntv = get<NegationType>(there))
{
std::optional<NormalizedType> tn;
std::shared_ptr<const NormalizedType> thereNormal = normalize(ntv->ty);
tn = negateNormal(*thereNormal);
if (!tn)
return NormalizationResult::False;
NormalizationResult res = unionNormals(here, *tn);
if (res != NormalizationResult::True)
return res;
}
else if (get<PendingExpansionType>(there) || get<TypeFunctionInstanceType>(there) || get<NoRefineType>(there))
{
}
else
LUAU_ASSERT(!"Unreachable");
for (auto& [tyvar, intersect] : here.tyvars)
{
NormalizationResult res = unionNormalWithTy(*intersect, there, seenTablePropPairs, seenSetTypes, tyvarIndex(tyvar));
if (res != NormalizationResult::True)
return res;
}
assertInvariant(here);
return NormalizationResult::True;
}
std::optional<NormalizedType> Normalizer::negateNormal(const NormalizedType& here)
{
consumeFuel();
NormalizedType result{builtinTypes};
result.isCacheable = here.isCacheable;
if (!get<NeverType>(here.tops))
{
return result;
}
if (!get<NeverType>(here.errors))
{
result.errors = here.errors;
return result;
}
if (get<NeverType>(here.booleans))
result.booleans = builtinTypes->booleanType;
else if (get<PrimitiveType>(here.booleans))
result.booleans = builtinTypes->neverType;
else if (auto stv = get<SingletonType>(here.booleans))
{
auto boolean = get<BooleanSingleton>(stv);
LUAU_ASSERT(boolean != nullptr);
if (boolean->value)
result.booleans = builtinTypes->falseType;
else
result.booleans = builtinTypes->trueType;
}
if (here.externTypes.isNever())
{
resetToTop(builtinTypes, result.externTypes);
}
else if (isTop(builtinTypes, result.externTypes))
{
result.externTypes.resetToNever();
}
else
{
TypeIds rootNegations{};
for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes)
{
if (hereParent != builtinTypes->externType)
rootNegations.insert(hereParent);
for (TypeId hereNegation : hereNegations)
unionExternTypesWithExternType(result.externTypes, hereNegation);
}
if (!rootNegations.empty())
result.externTypes.pushPair(builtinTypes->externType, std::move(rootNegations));
}
result.nils = get<NeverType>(here.nils) ? builtinTypes->nilType : builtinTypes->neverType;
result.numbers = get<NeverType>(here.numbers) ? builtinTypes->numberType : builtinTypes->neverType;
if (FFlag::LuauIntegerType)
result.integers = get<NeverType>(here.integers) ? builtinTypes->integerType : builtinTypes->neverType;
result.strings = here.strings;
result.strings.isCofinite = !result.strings.isCofinite;
result.threads = get<NeverType>(here.threads) ? builtinTypes->threadType : builtinTypes->neverType;
result.buffers = get<NeverType>(here.buffers) ? builtinTypes->bufferType : builtinTypes->neverType;
if (here.functions.isNever())
result.functions.resetToTop();
else if (here.functions.isTop)
result.functions.resetToNever();
else
return std::nullopt;
if (here.tables.empty())
result.tables.insert(builtinTypes->tableType);
else if (here.tables.size() == 1 && here.tables.front() == builtinTypes->tableType)
result.tables.clear();
else
return std::nullopt;
return result;
}
TypeIds Normalizer::negateAll(const TypeIds& theres)
{
consumeFuel();
TypeIds tys;
for (TypeId there : theres)
tys.insert(negate(there));
return tys;
}
TypeId Normalizer::negate(TypeId there)
{
consumeFuel();
there = follow(there);
if (get<AnyType>(there))
return there;
else if (get<UnknownType>(there))
return builtinTypes->neverType;
else if (get<NeverType>(there))
return builtinTypes->unknownType;
else if (auto ntv = get<NegationType>(there))
return ntv->ty;
else if (auto utv = get<UnionType>(there))
{
std::vector<TypeId> parts;
for (TypeId option : utv)
parts.push_back(negate(option));
return arena->addType(IntersectionType{std::move(parts)});
}
else if (auto itv = get<IntersectionType>(there))
{
std::vector<TypeId> options;
for (TypeId part : itv)
options.push_back(negate(part));
return arena->addType(UnionType{std::move(options)});
}
else
return there;
}
void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty)
{
consumeFuel();
const PrimitiveType* ptv = get<PrimitiveType>(follow(ty));
LUAU_ASSERT(ptv);
switch (ptv->type)
{
case PrimitiveType::NilType:
here.nils = builtinTypes->neverType;
break;
case PrimitiveType::Boolean:
here.booleans = builtinTypes->neverType;
break;
case PrimitiveType::Number:
here.numbers = builtinTypes->neverType;
break;
case PrimitiveType::Integer:
here.integers = builtinTypes->neverType;
break;
case PrimitiveType::String:
here.strings.resetToNever();
break;
case PrimitiveType::Thread:
here.threads = builtinTypes->neverType;
break;
case PrimitiveType::Buffer:
here.buffers = builtinTypes->neverType;
break;
case PrimitiveType::Function:
here.functions.resetToNever();
break;
case PrimitiveType::Table:
here.tables.clear();
break;
}
}
void Normalizer::subtractSingleton(NormalizedType& here, TypeId ty)
{
consumeFuel();
const SingletonType* stv = get<SingletonType>(ty);
LUAU_ASSERT(stv);
if (const StringSingleton* ss = get<StringSingleton>(stv))
{
if (here.strings.isCofinite)
here.strings.singletons.insert({ss->value, ty});
else
{
auto it = here.strings.singletons.find(ss->value);
if (it != here.strings.singletons.end())
here.strings.singletons.erase(it);
}
}
else if (const BooleanSingleton* bs = get<BooleanSingleton>(stv))
{
if (get<NeverType>(here.booleans))
{
}
else if (get<PrimitiveType>(here.booleans))
here.booleans = bs->value ? builtinTypes->falseType : builtinTypes->trueType;
else if (auto hereSingleton = get<SingletonType>(here.booleans))
{
const BooleanSingleton* hereBooleanSingleton = get<BooleanSingleton>(hereSingleton);
LUAU_ASSERT(hereBooleanSingleton);
if (bs->value == hereBooleanSingleton->value)
here.booleans = builtinTypes->neverType;
}
else
LUAU_ASSERT(!"Unreachable");
}
else
LUAU_ASSERT(!"Unreachable");
}
TypeId Normalizer::intersectionOfTops(TypeId here, TypeId there)
{
consumeFuel();
LUAU_ASSERT((is<NeverType, UnknownType, AnyType>(here)));
LUAU_ASSERT((is<NeverType, UnknownType, AnyType>(there)));
if (get<NeverType>(here) || get<NeverType>(there))
return builtinTypes->neverType;
if (get<AnyType>(here) || get<AnyType>(there))
return builtinTypes->anyType;
return builtinTypes->unknownType;
}
TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there)
{
consumeFuel();
if (get<NeverType>(here))
return here;
if (get<NeverType>(there))
return there;
if (const BooleanSingleton* hbool = get<BooleanSingleton>(get<SingletonType>(here)))
if (const BooleanSingleton* tbool = get<BooleanSingleton>(get<SingletonType>(there)))
return (hbool->value == tbool->value ? here : builtinTypes->neverType);
else
return here;
else
return there;
}
void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres)
{
consumeFuel();
if (theres.isNever())
{
heres.resetToNever();
return;
}
else if (isTop(builtinTypes, theres))
{
return;
}
for (const TypeId thereTy : theres.ordering)
{
const TypeIds& thereNegations = theres.externTypes.at(thereTy);
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (isSubclass(thereTy, hereTy))
{
TypeIds negations = std::move(hereNegations);
for (auto nIt = negations.begin(); nIt != negations.end();)
{
if (!isSubclass(*nIt, thereTy))
{
nIt = negations.erase(nIt);
}
else
{
++nIt;
}
}
unionExternTypes(negations, thereNegations);
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
heres.pushPair(thereTy, std::move(negations));
break;
}
else if (isSubclass(hereTy, thereTy))
{
TypeIds negations = thereNegations;
bool erasedHere = false;
for (auto nIt = negations.begin(); nIt != negations.end();)
{
if (isSubclass(hereTy, *nIt))
{
heres.externTypes.erase(hereTy);
it = heres.ordering.erase(it);
erasedHere = true;
break;
}
if (!isSubclass(*nIt, hereTy))
nIt = negations.erase(nIt);
else
++nIt;
}
if (!erasedHere)
{
unionExternTypes(hereNegations, negations);
++it;
}
}
else if (hereTy == thereTy)
{
unionExternTypes(hereNegations, thereNegations);
break;
}
else
{
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
}
}
}
}
void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there)
{
consumeFuel();
for (auto it = heres.ordering.begin(); it != heres.ordering.end();)
{
TypeId hereTy = *it;
const TypeIds& hereNegations = heres.externTypes.at(hereTy);
if (hereTy == there)
{
++it;
}
else if (isSubclass(there, hereTy))
{
TypeIds negations = std::move(hereNegations);
bool emptyIntersectWithNegation = false;
for (auto nIt = negations.begin(); nIt != negations.end();)
{
if (isSubclass(there, *nIt))
{
emptyIntersectWithNegation = true;
break;
}
if (!isSubclass(*nIt, there))
{
nIt = negations.erase(nIt);
}
else
{
++nIt;
}
}
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
if (!emptyIntersectWithNegation)
heres.pushPair(there, std::move(negations));
break;
}
else if (isSubclass(hereTy, there))
{
return;
}
else
{
it = heres.ordering.erase(it);
heres.externTypes.erase(hereTy);
}
}
}
void Normalizer::intersectExternTypesWithShape(NormalizedExternType& heres, TypeId there)
{
LUAU_ASSERT(FFlag::LuauExternTypesNormalizeWithShapes);
consumeFuel();
auto shape = get<TableType>(there);
if (!shape)
return;
bool isCoincident = true;
for (const auto& [name, shapeProp] : shape->props)
{
for (auto it = heres.ordering.begin(); it != heres.ordering.end(); it++)
{
TypeId hereTy = *it;
ExternType* externTy = getMutable<ExternType>(hereTy);
auto found = externTy->props.find(name);
if (found == externTy->props.end())
{
isCoincident = false;
continue;
}
Property prop = found->second;
if (prop.readTy && shapeProp.readTy)
{
if (isIntersectionInhabited(*prop.readTy, *shapeProp.readTy) != NormalizationResult::True)
{
heres.resetToNever();
return;
}
if (relate(*prop.readTy, *shapeProp.readTy) != Relation::Coincident)
isCoincident = false;
}
if (prop.writeTy && shapeProp.writeTy)
{
if (relate(*prop.writeTy, *shapeProp.writeTy) != Relation::Coincident)
isCoincident = false;
}
}
}
if (!isCoincident)
heres.shapeExtensions.insert(there);
}
void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there)
{
consumeFuel();
if (there.isString())
return;
else if (here.isString())
{
here.singletons.clear();
for (const auto& [key, type] : there.singletons)
here.singletons[key] = type;
here.isCofinite = here.isCofinite && there.isCofinite;
}
else if (here.isIntersection() && there.isIntersection())
{
here.isCofinite = true;
for (const auto& [key, type] : there.singletons)
here.singletons[key] = type;
}
else if (here.isUnion() && there.isIntersection())
{
here.isCofinite = false;
for (const auto& [key, _] : there.singletons)
here.singletons.erase(key);
}
else if (here.isIntersection() && there.isUnion())
{
here.isCofinite = false;
std::map<std::string, TypeId> result(there.singletons);
for (const auto& [key, _] : here.singletons)
result.erase(key);
here.singletons = result;
}
else if (here.isUnion() && there.isUnion())
{
here.isCofinite = false;
std::map<std::string, TypeId> result;
result.insert(here.singletons.begin(), here.singletons.end());
result.insert(there.singletons.begin(), there.singletons.end());
for (auto it = result.begin(); it != result.end();)
if (!here.singletons.count(it->first) || !there.singletons.count(it->first))
it = result.erase(it);
else
++it;
here.singletons = result;
}
else
LUAU_ASSERT(0 && "Internal Error - unrecognized case");
}
std::optional<TypePackId> Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there)
{
FuelInitializer fi{NotNull{this}};
try
{
return intersectionOfTypePacks_INTERNAL(here, there);
}
catch (const NormalizerHitLimits&)
{
return std::nullopt;
}
}
std::optional<TypePackId> Normalizer::intersectionOfTypePacks_INTERNAL(TypePackId here, TypePackId there)
{
consumeFuel();
if (here == there)
return here;
std::vector<TypeId> head;
std::optional<TypePackId> tail;
bool hereSubThere = true;
bool thereSubHere = true;
TypePackIterator ith = begin(here);
TypePackIterator itt = begin(there);
while (ith != end(here) && itt != end(there))
{
TypeId hty = *ith;
TypeId tty = *itt;
TypeId ty = intersectionType(hty, tty);
if (ty != hty)
hereSubThere = false;
if (ty != tty)
thereSubHere = false;
head.push_back(ty);
ith++;
itt++;
}
auto dealWithDifferentArities =
[&](TypePackIterator& ith, TypePackIterator itt, TypePackId here, TypePackId there, bool& hereSubThere, bool& thereSubHere)
{
if (ith != end(here))
{
TypeId tty = builtinTypes->nilType;
if (std::optional<TypePackId> ttail = itt.tail())
{
if (const VariadicTypePack* tvtp = get<VariadicTypePack>(*ttail))
tty = tvtp->ty;
else
return false;
}
else
return false;
while (ith != end(here))
{
TypeId hty = *ith;
TypeId ty = intersectionType(hty, tty);
if (ty != hty)
hereSubThere = false;
if (ty != tty)
thereSubHere = false;
head.push_back(ty);
ith++;
}
}
return true;
};
if (!dealWithDifferentArities(ith, itt, here, there, hereSubThere, thereSubHere))
return std::nullopt;
if (!dealWithDifferentArities(itt, ith, there, here, thereSubHere, hereSubThere))
return std::nullopt;
if (std::optional<TypePackId> htail = ith.tail())
{
if (std::optional<TypePackId> ttail = itt.tail())
{
if (*htail == *ttail)
tail = htail;
else if (const VariadicTypePack* hvtp = get<VariadicTypePack>(*htail))
{
if (const VariadicTypePack* tvtp = get<VariadicTypePack>(*ttail))
{
TypeId ty = intersectionType(hvtp->ty, tvtp->ty);
if (ty != hvtp->ty)
thereSubHere = false;
if (ty != tvtp->ty)
hereSubThere = false;
bool hidden = hvtp->hidden & tvtp->hidden;
tail = arena->addTypePack(VariadicTypePack{ty, hidden});
}
else
return std::nullopt;
}
else
return std::nullopt;
}
else if (get<VariadicTypePack>(*htail))
hereSubThere = false;
else
return std::nullopt;
}
else if (std::optional<TypePackId> ttail = itt.tail())
{
if (get<VariadicTypePack>(*ttail))
thereSubHere = false;
else
return std::nullopt;
}
if (hereSubThere)
return here;
else if (thereSubHere)
return there;
if (!head.empty())
return arena->addTypePack(TypePack{std::move(head), tail});
else if (tail)
return *tail;
else
return arena->addTypePack({});
}
std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSet)
{
consumeFuel();
if (here == there)
return here;
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (sharedState->counters.recursionLimit > 0 && sharedState->counters.recursionLimit < sharedState->counters.recursionCount)
return std::nullopt;
if (isPrim(here, PrimitiveType::Table))
return there;
else if (isPrim(there, PrimitiveType::Table))
return here;
if (get<NeverType>(here))
return there;
else if (get<NeverType>(there))
return here;
else if (get<AnyType>(here))
return there;
else if (get<AnyType>(there))
return here;
TypeId htable = here;
TypeId hmtable = nullptr;
if (const MetatableType* hmtv = get<MetatableType>(here))
{
htable = follow(hmtv->table);
hmtable = follow(hmtv->metatable);
}
TypeId ttable = there;
TypeId tmtable = nullptr;
if (const MetatableType* tmtv = get<MetatableType>(there))
{
ttable = follow(tmtv->table);
tmtable = follow(tmtv->metatable);
}
const TableType* httv = get<TableType>(htable);
if (!httv)
return std::nullopt;
const TableType* tttv = get<TableType>(ttable);
if (!tttv)
return std::nullopt;
if (httv->state == TableState::Free || tttv->state == TableState::Free)
return std::nullopt;
if (httv->state == TableState::Generic || tttv->state == TableState::Generic)
return std::nullopt;
TableState state = httv->state;
if (tttv->state == TableState::Unsealed)
state = tttv->state;
TypeLevel level = max(httv->level, tttv->level);
Scope* scope = max(httv->scope, tttv->scope);
std::unique_ptr<TableType> result = nullptr;
bool hereSubThere = true;
bool thereSubHere = true;
for (const auto& [name, hprop] : httv->props)
{
Property prop = hprop;
auto tfound = tttv->props.find(name);
if (tfound == tttv->props.end())
thereSubHere = false;
else
{
const auto& [_name, tprop] = *tfound;
if (useNewLuauSolver())
{
if (hprop.readTy.has_value())
{
if (tprop.readTy.has_value())
{
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
if (get<NeverType>(ty))
return {builtinTypes->neverType};
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
else
{
prop.readTy = *hprop.readTy;
thereSubHere = false;
}
}
else if (tprop.readTy.has_value())
{
prop.readTy = *tprop.readTy;
hereSubThere = false;
}
if (hprop.writeTy.has_value())
{
if (tprop.writeTy.has_value())
{
prop.writeTy = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.writeTy, *tprop.writeTy).result;
hereSubThere &= (prop.writeTy == hprop.writeTy);
thereSubHere &= (prop.writeTy == tprop.writeTy);
}
else
{
prop.writeTy = *hprop.writeTy;
thereSubHere = false;
}
}
else if (tprop.writeTy.has_value())
{
prop.writeTy = *tprop.writeTy;
hereSubThere = false;
}
}
else
{
prop.setType(intersectionType(hprop.type_DEPRECATED(), tprop.type_DEPRECATED()));
hereSubThere &= (prop.type_DEPRECATED() == hprop.type_DEPRECATED());
thereSubHere &= (prop.type_DEPRECATED() == tprop.type_DEPRECATED());
}
}
if (prop.readTy || prop.writeTy)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level, scope});
result->props[name] = prop;
}
}
for (const auto& [name, tprop] : tttv->props)
{
if (httv->props.count(name) == 0)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level, scope});
result->props[name] = tprop;
hereSubThere = false;
}
}
if (httv->indexer && tttv->indexer)
{
TypeId index = unionType(httv->indexer->indexType, tttv->indexer->indexType);
TypeId indexResult = intersectionType(httv->indexer->indexResultType, tttv->indexer->indexResultType);
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = {index, indexResult};
hereSubThere &= (httv->indexer->indexType == index) && (httv->indexer->indexResultType == indexResult);
thereSubHere &= (tttv->indexer->indexType == index) && (tttv->indexer->indexResultType == indexResult);
}
else if (httv->indexer)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = httv->indexer;
thereSubHere = false;
}
else if (tttv->indexer)
{
if (!result.get())
result = std::make_unique<TableType>(TableType{state, level, scope});
result->indexer = tttv->indexer;
hereSubThere = false;
}
TypeId table;
if (hereSubThere)
table = htable;
else if (thereSubHere)
table = ttable;
else
{
if (result.get())
table = arena->addType(std::move(*result));
else
table = arena->addType(TableType{state, level, scope});
}
if (tmtable && hmtable)
{
if (std::optional<TypeId> mtable = intersectionOfTables(hmtable, tmtable, seenTablePropPairs, seenSet))
{
if (table == htable && *mtable == hmtable)
return here;
else if (table == ttable && *mtable == tmtable)
return there;
else
return arena->addType(MetatableType{table, *mtable});
}
else
return std::nullopt;
}
else if (hmtable)
{
if (table == htable)
return here;
else
return arena->addType(MetatableType{table, hmtable});
}
else if (tmtable)
{
if (table == ttable)
return there;
else
return arena->addType(MetatableType{table, tmtable});
}
else
return table;
}
void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set<TypeId>& seenSetTypes)
{
consumeFuel();
TypeIds tmp;
for (TypeId here : heres)
{
if (std::optional<TypeId> inter = intersectionOfTables(here, there, seenTablePropPairs, seenSetTypes))
tmp.insert(*inter);
}
heres.retain(tmp);
heres.insert(tmp.begin(), tmp.end());
}
void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres)
{
consumeFuel();
TypeIds tmp;
for (TypeId here : heres)
{
for (TypeId there : theres)
{
Set<TypeId> seenSetTypes{nullptr};
SeenTablePropPairs seenTablePropPairs{{nullptr, nullptr}};
if (std::optional<TypeId> inter = intersectionOfTables(here, there, seenTablePropPairs, seenSetTypes))
tmp.insert(*inter);
}
}
heres.retain(tmp);
heres.insert(tmp.begin(), tmp.end());
}
std::optional<TypeId> Normalizer::intersectionOfFunctions(TypeId here, TypeId there)
{
consumeFuel();
const FunctionType* hftv = get<FunctionType>(here);
LUAU_ASSERT(hftv);
const FunctionType* tftv = get<FunctionType>(there);
LUAU_ASSERT(tftv);
if (hftv->generics != tftv->generics)
return std::nullopt;
if (hftv->genericPacks != tftv->genericPacks)
return std::nullopt;
TypePackId argTypes;
TypePackId retTypes;
if (hftv->retTypes == tftv->retTypes)
{
std::optional<TypePackId> argTypesOpt = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
if (!argTypesOpt)
return std::nullopt;
argTypes = *argTypesOpt;
retTypes = hftv->retTypes;
}
else if (hftv->argTypes == tftv->argTypes)
{
std::optional<TypePackId> retTypesOpt = intersectionOfTypePacks_INTERNAL(hftv->argTypes, tftv->argTypes);
if (!retTypesOpt)
return std::nullopt;
argTypes = hftv->argTypes;
retTypes = *retTypesOpt;
}
else
return std::nullopt;
if (argTypes == hftv->argTypes && retTypes == hftv->retTypes)
return here;
if (argTypes == tftv->argTypes && retTypes == tftv->retTypes)
return there;
FunctionType result{argTypes, retTypes};
result.generics = hftv->generics;
result.genericPacks = hftv->genericPacks;
return arena->addType(std::move(result));
}
std::optional<TypeId> Normalizer::unionSaturatedFunctions(TypeId here, TypeId there)
{
consumeFuel();
const FunctionType* hftv = get<FunctionType>(here);
if (!hftv)
return std::nullopt;
const FunctionType* tftv = get<FunctionType>(there);
if (!tftv)
return std::nullopt;
if (hftv->generics != tftv->generics)
return std::nullopt;
if (hftv->genericPacks != tftv->genericPacks)
return std::nullopt;
std::optional<TypePackId> argTypes = unionOfTypePacks(hftv->argTypes, tftv->argTypes);
if (!argTypes)
return std::nullopt;
std::optional<TypePackId> retTypes = unionOfTypePacks(hftv->retTypes, tftv->retTypes);
if (!retTypes)
return std::nullopt;
FunctionType result{*argTypes, *retTypes};
result.generics = hftv->generics;
result.genericPacks = hftv->genericPacks;
return arena->addType(std::move(result));
}
void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, TypeId there)
{
consumeFuel();
if (heres.isNever())
return;
heres.isTop = false;
for (auto it = heres.parts.begin(); it != heres.parts.end();)
{
TypeId here = *it;
if (get<ErrorType>(here))
it++;
else if (std::optional<TypeId> tmp = intersectionOfFunctions(here, there))
{
heres.parts.erase(it);
heres.parts.insert(*tmp);
return;
}
else
it++;
}
TypeIds tmps;
for (TypeId here : heres.parts)
{
if (std::optional<TypeId> tmp = unionSaturatedFunctions(here, there))
tmps.insert(*tmp);
}
heres.parts.insert(there);
heres.parts.insert(tmps.begin(), tmps.end());
}
void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres)
{
consumeFuel();
if (heres.isNever())
return;
else if (theres.isNever())
{
heres.resetToNever();
return;
}
else
{
for (TypeId there : theres.parts)
intersectFunctionsWithFunction(heres, there);
}
}
NormalizationResult Normalizer::intersectTyvarsWithTy(
NormalizedTyvars& here,
TypeId there,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSetTypes
)
{
consumeFuel();
for (auto it = here.begin(); it != here.end();)
{
NormalizedType& inter = *it->second;
NormalizationResult res = intersectNormalWithTy(inter, there, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
return res;
if (isShallowInhabited(inter))
++it;
else
it = here.erase(it);
}
return NormalizationResult::True;
}
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
consumeFuel();
if (!get<NeverType>(there.tops))
{
here.tops = intersectionOfTops(here.tops, there.tops);
return NormalizationResult::True;
}
else if (!get<NeverType>(here.tops))
{
clearNormal(here);
return unionNormals(here, there, ignoreSmallerTyvars);
}
for (auto& [tyvar, inter] : there.tyvars)
{
int index = tyvarIndex(tyvar);
if (ignoreSmallerTyvars < index)
{
auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique<NormalizedType>(NormalizedType{builtinTypes}));
if (fresh)
{
NormalizationResult res = unionNormals(*found->second, here, index);
if (res != NormalizationResult::True)
return res;
}
}
}
here.booleans = intersectionOfBools(here.booleans, there.booleans);
intersectExternTypes(here.externTypes, there.externTypes);
here.errors = (get<NeverType>(there.errors) ? there.errors : here.errors);
here.nils = (get<NeverType>(there.nils) ? there.nils : here.nils);
here.numbers = (get<NeverType>(there.numbers) ? there.numbers : here.numbers);
if (FFlag::LuauIntegerType)
here.integers = (get<NeverType>(there.integers) ? there.integers : here.integers);
intersectStrings(here.strings, there.strings);
here.threads = (get<NeverType>(there.threads) ? there.threads : here.threads);
here.buffers = (get<NeverType>(there.buffers) ? there.buffers : here.buffers);
intersectFunctions(here.functions, there.functions);
intersectTables(here.tables, there.tables);
for (auto it = here.tyvars.begin(); it != here.tyvars.end();)
{
TypeId tyvar = it->first;
NormalizedType& inter = *it->second;
int index = tyvarIndex(tyvar);
LUAU_ASSERT(ignoreSmallerTyvars < index);
auto found = there.tyvars.find(tyvar);
if (found == there.tyvars.end())
{
NormalizationResult res = intersectNormals(inter, there, index);
if (res != NormalizationResult::True)
return res;
}
else
{
NormalizationResult res = intersectNormals(inter, *found->second, index);
if (res != NormalizationResult::True)
return res;
}
if (isShallowInhabited(inter))
it++;
else
it = here.tyvars.erase(it);
}
return NormalizationResult::True;
}
NormalizationResult Normalizer::intersectNormalWithTy(
NormalizedType& here,
TypeId there,
SeenTablePropPairs& seenTablePropPairs,
Set<TypeId>& seenSetTypes
)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
consumeFuel();
there = follow(there);
if (get<AnyType>(there) || get<UnknownType>(there))
{
here.tops = intersectionOfTops(here.tops, there);
return NormalizationResult::True;
}
else if (!get<NeverType>(here.tops))
{
clearNormal(here);
return unionNormalWithTy(here, there, seenTablePropPairs, seenSetTypes);
}
else if (const UnionType* utv = get<UnionType>(there))
{
NormalizedType norm{builtinTypes};
for (UnionTypeIterator it = begin(utv); it != end(utv); ++it)
{
NormalizationResult res = unionNormalWithTy(norm, *it, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
return res;
}
return intersectNormals(here, norm);
}
else if (const IntersectionType* itv = get<IntersectionType>(there))
{
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
{
NormalizationResult res = intersectNormalWithTy(here, *it, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
return res;
}
return NormalizationResult::True;
}
else if (get<GenericType>(there) || get<FreeType>(there) || get<BlockedType>(there) || get<PendingExpansionType>(there) ||
get<TypeFunctionInstanceType>(there))
{
NormalizedType thereNorm{builtinTypes};
NormalizedType topNorm{builtinTypes};
topNorm.tops = builtinTypes->unknownType;
thereNorm.tyvars.insert_or_assign(there, std::make_unique<NormalizedType>(std::move(topNorm)));
here.isCacheable = false;
return intersectNormals(here, thereNorm);
}
NormalizedTyvars tyvars = std::move(here.tyvars);
if (get<FunctionType>(there))
{
NormalizedFunctionType functions = std::move(here.functions);
clearNormal(here);
intersectFunctionsWithFunction(functions, there);
here.functions = std::move(functions);
}
else if (get<TableType>(there) || get<MetatableType>(there))
{
if (useNewLuauSolver())
{
NormalizedExternType externTypes = std::move(here.externTypes);
TypeIds tables = std::move(here.tables);
clearNormal(here);
if (FFlag::LuauExternTypesNormalizeWithShapes)
{
if (externTypes.isNever())
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
else
intersectExternTypesWithShape(externTypes, there);
}
else
{
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
}
here.tables = std::move(tables);
here.externTypes = std::move(externTypes);
}
else
{
TypeIds tables = std::move(here.tables);
clearNormal(here);
intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes);
here.tables = std::move(tables);
}
}
else if (get<ExternType>(there))
{
NormalizedExternType nct = std::move(here.externTypes);
clearNormal(here);
intersectExternTypesWithExternType(nct, there);
here.externTypes = std::move(nct);
}
else if (get<ErrorType>(there))
{
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : there;
}
else if (const PrimitiveType* ptv = get<PrimitiveType>(there))
{
TypeId booleans = here.booleans;
TypeId nils = here.nils;
TypeId numbers = here.numbers;
TypeId integers = here.integers;
NormalizedStringType strings = std::move(here.strings);
NormalizedFunctionType functions = std::move(here.functions);
TypeId threads = here.threads;
TypeId buffers = here.buffers;
TypeIds tables = std::move(here.tables);
clearNormal(here);
if (ptv->type == PrimitiveType::Boolean)
here.booleans = booleans;
else if (ptv->type == PrimitiveType::NilType)
here.nils = nils;
else if (ptv->type == PrimitiveType::Number)
here.numbers = numbers;
else if (FFlag::LuauIntegerType && (ptv->type == PrimitiveType::Integer))
here.integers = integers;
else if (ptv->type == PrimitiveType::String)
here.strings = std::move(strings);
else if (ptv->type == PrimitiveType::Thread)
here.threads = threads;
else if (ptv->type == PrimitiveType::Buffer)
here.buffers = buffers;
else if (ptv->type == PrimitiveType::Function)
here.functions = std::move(functions);
else if (ptv->type == PrimitiveType::Table)
here.tables = std::move(tables);
else
LUAU_ASSERT(!"Unreachable");
}
else if (const SingletonType* stv = get<SingletonType>(there))
{
TypeId booleans = here.booleans;
NormalizedStringType strings = std::move(here.strings);
clearNormal(here);
if (get<BooleanSingleton>(stv))
here.booleans = intersectionOfBools(booleans, there);
else if (const StringSingleton* sstv = get<StringSingleton>(stv))
{
if (strings.includes(sstv->value))
here.strings.singletons.insert({sstv->value, there});
}
else
LUAU_ASSERT(!"Unreachable");
}
else if (const NegationType* ntv = get<NegationType>(there))
{
TypeId t = follow(ntv->ty);
if (get<PrimitiveType>(t))
subtractPrimitive(here, ntv->ty);
else if (get<SingletonType>(t))
subtractSingleton(here, follow(ntv->ty));
else if (get<ExternType>(t))
{
NormalizationResult res = intersectNormalWithNegationTy(t, here);
if (shouldEarlyExit(res))
return res;
}
else if (const UnionType* itv = get<UnionType>(t))
{
for (TypeId part : itv->options)
{
NormalizationResult res = intersectNormalWithNegationTy(part, here);
if (shouldEarlyExit(res))
return res;
}
}
else if (get<AnyType>(t))
{
return NormalizationResult::True;
}
else if (get<NoRefineType>(t))
{
return NormalizationResult::True;
}
else if (get<NeverType>(t))
{
return NormalizationResult::True;
}
else if (get<UnknownType>(t))
{
clearNormal(here);
return NormalizationResult::True;
}
else if (get<ErrorType>(t))
{
TypeId errors = here.errors;
clearNormal(here);
here.errors = get<ErrorType>(errors) ? errors : t;
}
else if (auto nt = get<NegationType>(t))
{
here.tyvars = std::move(tyvars);
return intersectNormalWithTy(here, nt->ty, seenTablePropPairs, seenSetTypes);
}
else
{
LUAU_ASSERT(!"Unimplemented");
}
}
else if (get<NeverType>(there))
{
here.externTypes.resetToNever();
}
else if (get<NoRefineType>(there))
{
return NormalizationResult::True;
}
else
LUAU_ASSERT(!"Unreachable");
NormalizationResult res = intersectTyvarsWithTy(tyvars, there, seenTablePropPairs, seenSetTypes);
if (res != NormalizationResult::True)
return res;
here.tyvars = std::move(tyvars);
return NormalizationResult::True;
}
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{
ty = follow(ty);
if (seen.contains(ty))
return;
seen.insert(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared(metatableTy->metatable, seen);
makeTableShared(metatableTy->table, seen);
}
}
void makeTableShared(TypeId ty)
{
DenseHashSet<TypeId> seen{nullptr};
makeTableShared(ty, seen);
}
TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
{
assertInvariant(norm);
if (!get<NeverType>(norm.tops))
return norm.tops;
std::vector<TypeId> result;
if (!get<NeverType>(norm.booleans))
result.push_back(norm.booleans);
if (isTop(builtinTypes, norm.externTypes))
{
result.push_back(builtinTypes->externType);
}
else if (!norm.externTypes.isNever())
{
std::vector<TypeId> parts;
parts.reserve(norm.externTypes.externTypes.size());
for (const TypeId normTy : norm.externTypes.ordering)
{
const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy);
if (normNegations.empty() && (!FFlag::LuauExternTypesNormalizeWithShapes || norm.externTypes.shapeExtensions.empty()))
{
parts.push_back(normTy);
}
else
{
std::vector<TypeId> intersection;
intersection.reserve(normNegations.size() + 1);
intersection.push_back(normTy);
for (TypeId negation : normNegations)
{
intersection.push_back(arena->addType(NegationType{negation}));
}
if (FFlag::LuauExternTypesNormalizeWithShapes)
{
for (TypeId shape : norm.externTypes.shapeExtensions)
{
intersection.push_back(shape);
}
}
parts.push_back(arena->addType(IntersectionType{std::move(intersection)}));
}
}
if (parts.size() == 1)
{
result.push_back(parts.at(0));
}
else if (parts.size() > 1)
{
result.push_back(arena->addType(UnionType{std::move(parts)}));
}
}
if (!get<NeverType>(norm.errors))
result.push_back(norm.errors);
if (norm.functions.isTop)
result.push_back(builtinTypes->functionType);
else if (!norm.functions.isNever())
{
if (norm.functions.parts.size() == 1)
result.push_back(*norm.functions.parts.begin());
else
{
std::vector<TypeId> parts;
parts.insert(parts.end(), norm.functions.parts.begin(), norm.functions.parts.end());
result.push_back(arena->addType(IntersectionType{std::move(parts)}));
}
}
if (!get<NeverType>(norm.nils))
result.push_back(norm.nils);
if (!get<NeverType>(norm.numbers))
result.push_back(norm.numbers);
if (FFlag::LuauIntegerType)
{
if (get<NeverType>(norm.integers) == nullptr)
result.push_back(norm.integers);
}
if (norm.strings.isString())
result.push_back(builtinTypes->stringType);
else if (norm.strings.isUnion())
{
for (auto& [_, ty] : norm.strings.singletons)
result.push_back(ty);
}
else if (norm.strings.isIntersection())
{
std::vector<TypeId> parts;
parts.push_back(builtinTypes->stringType);
for (const auto& [name, ty] : norm.strings.singletons)
parts.push_back(arena->addType(NegationType{ty}));
result.push_back(arena->addType(IntersectionType{std::move(parts)}));
}
if (!get<NeverType>(norm.threads))
result.push_back(builtinTypes->threadType);
if (!get<NeverType>(norm.buffers))
result.push_back(builtinTypes->bufferType);
if (useNewLuauSolver())
{
result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables)
result.push_back(table);
}
else
result.insert(result.end(), norm.tables.begin(), norm.tables.end());
for (auto& [tyvar, intersect] : norm.tyvars)
{
if (get<NeverType>(intersect->tops))
{
TypeId ty = typeFromNormal(*intersect);
result.push_back(addIntersection(NotNull{arena}, builtinTypes, {tyvar, ty}));
}
else
result.push_back(tyvar);
}
if (result.size() == 0)
return builtinTypes->neverType;
else if (result.size() == 1)
return result[0];
else
return arena->addType(UnionType{std::move(result)});
}
bool Normalizer::initializeFuel()
{
if (fuel)
return false;
fuel = FInt::LuauNormalizerInitialFuel;
return true;
}
void Normalizer::clearFuel()
{
fuel = std::nullopt;
}
void Normalizer::consumeFuel()
{
if (fuel)
{
(*fuel)--;
if (fuel <= 0)
throw NormalizerHitLimits();
}
}
bool isSubtype(
TypeId subTy,
TypeId superTy,
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> scope,
NotNull<Normalizer> normalizer,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<InternalErrorReporter> reporter
)
{
Subtyping subtyping{builtinTypes, arena, normalizer, typeFunctionRuntime, reporter};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
bool isSubtype_DEPRECATED(
TypeId subTy,
TypeId superTy,
NotNull<Scope> scope,
NotNull<BuiltinTypes> builtinTypes,
InternalErrorReporter& ice,
SolverMode solverMode
)
{
UnifierSharedState sharedState{&ice};
TypeArena arena;
TypeCheckLimits limits;
TypeFunctionRuntime typeFunctionRuntime{
NotNull{&ice}, NotNull{&limits}
};
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}, solverMode};
if (solverMode == SolverMode::New)
{
Subtyping subtyping{builtinTypes, NotNull{&arena}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}};
return subtyping.isSubtype(subTy, superTy, scope).isSubtype;
}
else
{
Unifier u{NotNull{&normalizer}, scope, Location{}, Covariant};
u.tryUnify(subTy, superTy);
return !u.failure;
}
}
}