#include "Luau/BuiltinTypeFunctions.h"
#include "Luau/Common.h"
#include "Luau/ConstraintSolver.h"
#include "Luau/Instantiation.h"
#include "Luau/OverloadResolution.h"
#include "Luau/Scope.h"
#include "Luau/Simplify.h"
#include "Luau/Subtyping.h"
#include "Luau/Type.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeUtils.h"
#include "Luau/Instantiation2.h"
#include "Luau/Unifier2.h"
#include "Luau/UserDefinedTypeFunction.h"
#include "Luau/VisitType.h"
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64)
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
LUAU_FASTFLAGVARIABLE(LuauTypeFunctionsCaptureNestedInstances)
LUAU_FASTFLAGVARIABLE(LuauTypeFunctionsAddFreeTypePackWithPositivePolarity)
LUAU_FASTFLAGVARIABLE(LuauThreadUniferStateThroughTypeFunctionReduction)
namespace Luau
{
namespace
{
template<typename F, typename... Args>
std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunctionApp(
F f,
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx,
Args&&... args
)
{
Reduction reductionStatus = Reduction::MaybeOk;
std::vector<TypeId> blockedTypes;
std::vector<TypeId> results;
size_t cartesianProductSize = 1;
const UnionType* firstUnion = nullptr;
size_t unionIndex = 0;
std::vector<TypeId> arguments = typeParams;
for (size_t i = 0; i < arguments.size(); ++i)
{
const UnionType* ut = get<UnionType>(follow(arguments[i]));
if (!ut)
continue;
if (!firstUnion && ut)
{
firstUnion = ut;
unionIndex = i;
}
cartesianProductSize *= std::distance(begin(ut), end(ut));
if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= cartesianProductSize)
return {{std::nullopt, Reduction::Erroneous, {}, {}}};
}
if (!firstUnion)
{
return std::nullopt;
}
for (TypeId option : firstUnion)
{
arguments[unionIndex] = option;
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
if (result.reductionStatus != Reduction::MaybeOk)
reductionStatus = result.reductionStatus;
if (reductionStatus != Reduction::MaybeOk || !result.result)
break;
else
results.push_back(*result.result);
}
if (reductionStatus != Reduction::MaybeOk || !blockedTypes.empty())
return {{std::nullopt, reductionStatus, std::move(blockedTypes), {}}};
if (!results.empty())
{
if (results.size() == 1)
return {{results[0], Reduction::MaybeOk, {}, {}}};
TypeId resultTy = ctx->arena->addType(
TypeFunctionInstanceType{
NotNull{&ctx->builtins->typeFunctions->unionFunc},
std::move(results),
{},
}
);
if (FFlag::LuauTypeFunctionsCaptureNestedInstances)
{
ctx->freshInstances.emplace_back(resultTy);
return {{resultTy, Reduction::MaybeOk}};
}
else
{
if (ctx->solver)
ctx->pushConstraint(ReduceConstraint{resultTy});
return {{resultTy, Reduction::MaybeOk, {}, {}, {}, {}, {resultTy}}};
}
}
return std::nullopt;
}
}
static std::optional<TypePackId> solveFunctionCall(NotNull<TypeFunctionContext> ctx, const Location& location, TypeId fnTy, TypePackId argsPack)
{
auto resolver = std::make_unique<OverloadResolver>(
ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->scope, ctx->ice, ctx->limits, location
);
DenseHashSet<TypeId> uniqueTypes{nullptr};
OverloadResolution resolution = resolver->resolveOverload(fnTy, argsPack, location, NotNull{&uniqueTypes}, false);
if (resolution.ok.empty() && resolution.potentialOverloads.empty())
return std::nullopt;
SelectedOverload selected = resolution.getUnambiguousOverload();
if (!selected.overload.has_value())
return std::nullopt;
TypePackId retPack = FFlag::LuauTypeFunctionsAddFreeTypePackWithPositivePolarity ? ctx->arena->freshTypePack(ctx->scope, Polarity::Positive)
: ctx->arena->freshTypePack(ctx->scope);
TypeId prospectiveFunction = ctx->arena->addType(FunctionType{argsPack, retPack});
Unifier2 unifier{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
const UnifyResult unifyResult = unifier.unify(*selected.overload, prospectiveFunction);
switch (unifyResult)
{
case Luau::UnifyResult::Ok:
break;
case Luau::UnifyResult::OccursCheckFailed:
return std::nullopt;
case Luau::UnifyResult::TooComplex:
return std::nullopt;
}
if (!unifier.genericSubstitutions.empty() || !unifier.genericPackSubstitutions.empty())
{
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
std::optional<TypePackId> subst = instantiate2(
ctx->arena, std::move(unifier.genericSubstitutions), std::move(unifier.genericPackSubstitutions), NotNull{&subtyping}, ctx->scope, retPack
);
if (!subst)
return std::nullopt;
else
retPack = *subst;
}
if (FFlag::LuauOverloadGetsInstantiated)
{
for (const auto& ty : unifier.newFreshTypes)
trackInteriorFreeType(ctx->scope, ty);
for (const auto& tp : unifier.newFreshTypePacks)
trackInteriorFreeTypePack(ctx->scope, tp);
}
return retPack;
}
TypeFunctionReductionResult<TypeId> notTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("not type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId ty = follow(typeParams.at(0));
if (ty == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isPending(ty, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
return *result;
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> lenTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("len type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId operandTy = follow(typeParams.at(0));
if (operandTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
if (!normTy || inhabited == NormalizationResult::HitLimits)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normTy->shouldSuppressErrors())
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
if (inhabited == NormalizationResult::False || normTy->isSubtypeOfString())
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
TypeId normalizedOperand = follow(ctx->normalizer->typeFromNormal(*normTy));
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(lenTypeFunction, instance, typeParams, packParams, ctx))
return *result;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
if (!mmType)
{
if (get<MetatableType>(normalizedOperand))
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({operandTy})))
return {std::nullopt, Reduction::Erroneous, {}, {}};
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> unmTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("unm type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId operandTy = follow(typeParams.at(0));
if (operandTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normTy->shouldSuppressErrors())
return {operandTy, Reduction::MaybeOk, {}, {}};
if (is<NeverType>(operandTy))
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (normTy->isExactlyNumber())
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(unmTypeFunction, instance, typeParams, packParams, ctx))
return *result;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__unm", Location{});
if (!mmType)
return {std::nullopt, Reduction::Erroneous, {}, {}};
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
auto result = solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({operandTy}));
if (!result)
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (auto ret = first(*result))
return {ret, Reduction::MaybeOk, {}, {}};
else
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
TypeFunctionContext::TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
: arena(cs->arena)
, builtins(cs->builtinTypes)
, scope(scope)
, normalizer(cs->normalizer)
, typeFunctionRuntime(cs->typeFunctionRuntime)
, ice(NotNull{&cs->iceReporter})
, limits(NotNull{&cs->limits})
, solver(cs.get())
, constraint(constraint.get())
{
}
NotNull<Constraint> TypeFunctionContext::pushConstraint(ConstraintV&& c) const
{
LUAU_ASSERT(solver);
NotNull<Constraint> newConstraint = solver->pushConstraint(scope, constraint ? constraint->location : Location{}, std::move(c));
if (constraint)
solver->inheritBlocks(NotNull{constraint}, newConstraint);
return newConstraint;
}
TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx,
const std::string metamethod
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
if (!normLhsTy || !normRhsTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(numericBinopTypeFunction, instance, typeParams, packParams, ctx, metamethod))
return *result;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, location);
bool reversed = false;
if (!mmType)
{
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, location);
reversed = true;
}
if (!mmType)
return {std::nullopt, Reduction::Erroneous, {}, {}};
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
TypePackId argPack = ctx->arena->addTypePack({lhsTy, rhsTy});
if (reversed)
{
TypePack* p = getMutable<TypePack>(argPack);
std::swap(p->head.front(), p->head.back());
}
std::optional<TypePackId> retPack = solveFunctionCall(ctx, location, *mmType, argPack);
if (!retPack.has_value())
return {std::nullopt, Reduction::Erroneous, {}, {}};
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *retPack, 1);
if (extracted.head.empty())
return {std::nullopt, Reduction::Erroneous, {}, {}};
return {extracted.head.front(), Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> addTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("add type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__add");
}
TypeFunctionReductionResult<TypeId> subTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("sub type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__sub");
}
TypeFunctionReductionResult<TypeId> mulTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("mul type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mul");
}
TypeFunctionReductionResult<TypeId> divTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("div type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__div");
}
TypeFunctionReductionResult<TypeId> idivTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("integer div type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__idiv");
}
TypeFunctionReductionResult<TypeId> powTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("pow type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__pow");
}
TypeFunctionReductionResult<TypeId> modTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("modulo type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mod");
}
TypeFunctionReductionResult<TypeId> concatTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("concat type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
if (!normLhsTy || !normRhsTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(concatTypeFunction, instance, typeParams, packParams, ctx))
return *result;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__concat", Location{});
bool reversed = false;
if (!mmType)
{
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__concat", Location{});
reversed = true;
}
if (!mmType)
return {std::nullopt, Reduction::Erroneous, {}, {}};
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
std::vector<TypeId> inferredArgs;
if (!reversed)
inferredArgs = {lhsTy, rhsTy};
else
inferredArgs = {rhsTy, lhsTy};
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack(std::move(inferredArgs))))
return {std::nullopt, Reduction::Erroneous, {}, {}};
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
}
namespace
{
bool isBlockedOrUnsolvedType(TypeId ty)
{
if (auto tfit = get<TypeFunctionInstanceType>(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved)
return true;
return is<BlockedType, PendingExpansionType>(ty);
}
}
TypeFunctionReductionResult<TypeId> andTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("and type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (follow(rhsTy) == instance && lhsTy != rhsTy)
return {lhsTy, Reduction::MaybeOk, {}, {}};
if (follow(lhsTy) == instance && lhsTy != rhsTy)
return {rhsTy, Reduction::MaybeOk, {}, {}};
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->falsyType);
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
std::vector<TypeId> blockedTypes{};
for (auto ty : filteredLhs.blockedTypes)
blockedTypes.push_back(ty);
for (auto ty : overallResult.blockedTypes)
blockedTypes.push_back(ty);
return {overallResult.result, Reduction::MaybeOk, std::move(blockedTypes), {}};
}
TypeFunctionReductionResult<TypeId> orTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("or type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (follow(rhsTy) == instance && lhsTy != rhsTy)
return {lhsTy, Reduction::MaybeOk, {}, {}};
if (follow(lhsTy) == instance && lhsTy != rhsTy)
return {rhsTy, Reduction::MaybeOk, {}, {}};
if (isBlockedOrUnsolvedType(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isBlockedOrUnsolvedType(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType);
SimplifyResult overallResult = simplifyUnion(ctx->builtins, ctx->arena, rhsTy, filteredLhs.result);
std::vector<TypeId> blockedTypes{};
for (auto ty : filteredLhs.blockedTypes)
blockedTypes.push_back(ty);
for (auto ty : overallResult.blockedTypes)
blockedTypes.push_back(ty);
return {overallResult.result, Reduction::MaybeOk, std::move(blockedTypes), {}};
}
static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx,
const std::string metamethod
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (isBlockedOrUnsolvedType(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isBlockedOrUnsolvedType(rhsTy))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
bool canSubmitConstraint = ctx->solver && ctx->constraint;
bool lhsFree = get<FreeType>(lhsTy) != nullptr;
bool rhsFree = get<FreeType>(rhsTy) != nullptr;
if (canSubmitConstraint)
{
if (lhsFree && isNumber(rhsTy))
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
else if (rhsFree && isNumber(lhsTy))
emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType);
}
lhsTy = follow(lhsTy);
rhsTy = follow(rhsTy);
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(comparisonTypeFunction, instance, typeParams, packParams, ctx, metamethod))
return *result;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{});
if (!mmType)
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{});
if (!mmType)
return {std::nullopt, Reduction::Erroneous, {}, {}};
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({lhsTy, rhsTy})))
return {std::nullopt, Reduction::Erroneous, {}, {}};
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> ltTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("lt type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__lt");
}
TypeFunctionReductionResult<TypeId> leTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("le type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__le");
}
TypeFunctionReductionResult<TypeId> eqTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("eq type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}};
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__eq", Location{});
if (!mmType)
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__eq", Location{});
NormalizationResult intersectInhabited = ctx->normalizer->isIntersectionInhabited(lhsTy, rhsTy);
if (!mmType)
{
if (intersectInhabited == NormalizationResult::True)
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
if (intersectInhabited == NormalizationResult::False)
{
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
return {ctx->builtins->falseType, Reduction::MaybeOk, {}, {}};
if (normLhsTy->isSubtypeOfBooleans() && normRhsTy->isSubtypeOfBooleans())
return {ctx->builtins->falseType, Reduction::MaybeOk, {}, {}};
}
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
mmType = follow(*mmType);
if (isPending(*mmType, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {*mmType}, {}};
if (!solveFunctionCall(ctx, ctx->constraint ? ctx->constraint->location : Location{}, *mmType, ctx->arena->addTypePack({lhsTy, rhsTy})))
return {std::nullopt, Reduction::Erroneous, {}, {}};
return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}};
}
struct FindRefinementBlockers : TypeOnceVisitor
{
DenseHashSet<TypeId> found{nullptr};
FindRefinementBlockers()
: TypeOnceVisitor("FindRefinementBlockers", true)
{
}
bool visit(TypeId ty, const BlockedType&) override
{
found.insert(ty);
return false;
}
bool visit(TypeId ty, const PendingExpansionType&) override
{
found.insert(ty);
return false;
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
};
struct ContainsRefinableType : TypeOnceVisitor
{
bool found = false;
ContainsRefinableType()
: TypeOnceVisitor("ContainsRefinableType", true)
{
}
bool visit(TypeId ty) override
{
found = true;
return false;
}
bool visit(TypeId Ty, const NoRefineType&) override
{
return false;
}
bool visit(TypeId ty, const TableType&) override
{
return !found;
}
bool visit(TypeId ty, const MetatableType&) override
{
return !found;
}
bool visit(TypeId ty, const FunctionType&) override
{
return !found;
}
bool visit(TypeId ty, const UnionType&) override
{
return !found;
}
bool visit(TypeId ty, const IntersectionType&) override
{
return !found;
}
bool visit(TypeId ty, const NegationType&) override
{
return !found;
}
};
namespace
{
bool isTruthyOrFalsyType(TypeId ty)
{
ty = follow(ty);
return isApproximatelyTruthyType(ty) || isApproximatelyFalsyType(ty);
}
struct RefineTypeScrubber : public Substitution
{
NotNull<TypeFunctionContext> ctx;
TypeId needle;
explicit RefineTypeScrubber(NotNull<TypeFunctionContext> ctx, TypeId needle)
: Substitution(ctx->arena)
, ctx{ctx}
, needle{needle}
{
}
bool isDirty(TypePackId tp) override
{
return false;
}
bool ignoreChildren(TypePackId tp) override
{
return false;
}
TypePackId clean(TypePackId tp) override
{
return tp;
}
bool isDirty(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
for (auto option : ut)
{
if (option == needle)
return true;
}
}
else if (auto it = get<IntersectionType>(ty))
{
for (auto part : it)
{
if (part == needle)
return true;
}
}
return ty == needle;
}
bool ignoreChildren(TypeId ty) override
{
return !is<UnionType, IntersectionType>(ty);
}
TypeId clean(TypeId ty) override
{
if (auto ut = get<UnionType>(ty))
{
TypeIds newOptions;
for (auto option : ut)
{
if (option != needle && !is<NeverType>(option))
newOptions.insert(option);
}
if (newOptions.empty())
return ctx->builtins->neverType;
else if (newOptions.size() == 1)
return *newOptions.begin();
else
return ctx->arena->addType(UnionType{newOptions.take()});
}
else if (auto it = get<IntersectionType>(ty))
{
TypeIds newParts;
for (auto part : it)
{
if (part != needle && !is<UnknownType>(part))
newParts.insert(part);
}
if (newParts.empty())
return ctx->builtins->unknownType;
else if (newParts.size() == 1)
return *newParts.begin();
else
return ctx->arena->addType(IntersectionType{newParts.take()});
}
else if (ty == needle)
return ctx->builtins->unknownType;
else
return ty;
}
};
bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen)
{
if (needle == haystack)
return true;
if (seen.contains(haystack))
return false;
seen.insert(haystack);
if (auto ut = get<UnionType>(haystack))
{
for (auto option : ut)
if (occurs(option, needle, seen))
return true;
}
if (auto it = get<UnionType>(haystack))
{
for (auto part : it)
if (occurs(part, needle, seen))
return true;
}
return false;
}
bool occurs(TypeId haystack, TypeId needle)
{
DenseHashSet<TypeId> seen{nullptr};
return occurs(haystack, needle, seen);
}
}
TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() < 2 || !packParams.empty())
{
ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId targetTy = follow(typeParams.at(0));
if (occurs(targetTy, instance))
{
RefineTypeScrubber rts{ctx, instance};
if (auto result = rts.substitute(targetTy))
targetTy = *result;
}
std::vector<TypeId> discriminantTypes;
for (size_t i = 1; i < typeParams.size(); i++)
{
auto discriminant = follow(typeParams[i]);
if (is<UnknownType, NoRefineType>(discriminant))
continue;
ContainsRefinableType crt;
crt.traverse(discriminant);
if (crt.found)
discriminantTypes.push_back(discriminant);
}
if (discriminantTypes.empty())
return {targetTy, {}};
const bool targetIsPending = isBlockedOrUnsolvedType(targetTy);
if (targetIsPending)
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
else
{
for (auto t : discriminantTypes)
{
if (isPending(t, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {t}, {}};
}
}
FindRefinementBlockers frb;
frb.traverse(targetTy);
if (!frb.found.empty())
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
int stepRefineCount = 0;
auto stepRefine = [&stepRefineCount, &ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
{
RecursionLimiter rl{"BuiltInTypeFunctions::stepRefine", &stepRefineCount, DFInt::LuauStepRefineRecursionLimit};
std::vector<TypeId> toBlock;
FindRefinementBlockers frb;
frb.traverse(discriminant);
if (!frb.found.empty())
return {nullptr, {frb.found.begin(), frb.found.end()}};
if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant))
return {*ty, {}};
if (auto negation = get<NegationType>(discriminant))
{
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
return {result.result, {}};
}
}
if (is<TableType>(target) || isTruthyOrFalsyType(discriminant))
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (std::all_of(
begin(result.blockedTypes),
end(result.blockedTypes),
[](TypeId v)
{
return is<FreeType, GenericType>(follow(v));
}
))
{
return {result.result, {}};
}
else
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
return {result.result, {}};
}
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(target);
if (!normIntersection || !normType)
return {nullptr, {}};
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
resultTy = addUnion(ctx->arena, ctx->builtins, {resultTy, ctx->builtins->errorType});
return {resultTy, {}};
};
TypeId target = targetTy;
while (!discriminantTypes.empty())
{
TypeId discriminant = discriminantTypes.back();
discriminant = follow(discriminant);
if (auto ut = get<UnionType>(discriminant))
{
TypeId workingType = ctx->builtins->neverType;
for (auto optionAsDiscriminant : ut->options)
{
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, workingType, optionAsDiscriminant);
if (!simplified.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
workingType = simplified.result;
}
discriminant = workingType;
}
if (auto ut = get<UnionType>(discriminant))
{
TypeId finalRefined = ctx->builtins->neverType;
for (auto optionAsDiscriminant : ut->options)
{
auto [refined, blocked] = stepRefine(target, follow(optionAsDiscriminant));
if (blocked.empty() && refined == nullptr)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (!blocked.empty())
return {std::nullopt, Reduction::MaybeOk, blocked, {}};
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, finalRefined, refined);
if (!simplified.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
finalRefined = simplified.result;
}
target = finalRefined;
discriminantTypes.pop_back();
continue;
}
auto [refined, blocked] = stepRefine(target, discriminant);
if (blocked.empty() && refined == nullptr)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (!blocked.empty())
return {std::nullopt, Reduction::MaybeOk, blocked, {}};
target = refined;
discriminantTypes.pop_back();
}
return {target, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> singletonTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("singleton type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId type = follow(typeParams.at(0));
if (isPending(type, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {type}, {}};
TypeId followed = type;
if (auto negation = get<NegationType>(followed))
followed = follow(negation->ty);
if (get<SingletonType>(followed) || isNil(followed))
return {type, Reduction::MaybeOk, {}, {}};
return {ctx->builtins->unknownType, Reduction::MaybeOk, {}, {}};
}
struct CollectUnionTypeOptions : TypeOnceVisitor
{
NotNull<TypeFunctionContext> ctx;
DenseHashSet<TypeId> options{nullptr};
DenseHashSet<TypeId> blockingTypes{nullptr};
explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor("CollectUnionTypeOptions", true)
, ctx(ctx)
{
}
bool visit(TypeId ty) override
{
options.insert(ty);
if (isPending(ty, ctx->solver))
blockingTypes.insert(ty);
return false;
}
bool visit(TypePackId tp) override
{
return false;
}
bool visit(TypeId ty, const UnionType& ut) override
{
return true;
}
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
{
if (tfit.function->name != ctx->builtins->typeFunctions->unionFunc.name)
{
options.insert(ty);
blockingTypes.insert(ty);
return false;
}
return true;
}
};
TypeFunctionReductionResult<TypeId> unionTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (!packParams.empty())
{
ctx->ice->ice("union type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
if (typeParams.size() == 1)
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
CollectUnionTypeOptions collector{ctx};
collector.traverse(instance);
if (!collector.blockingTypes.empty())
{
std::vector<TypeId> blockingTypes{collector.blockingTypes.begin(), collector.blockingTypes.end()};
return {std::nullopt, Reduction::MaybeOk, std::move(blockingTypes), {}};
}
TypeId resultTy = ctx->builtins->neverType;
for (auto ty : collector.options)
{
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
resultTy = result.result;
}
return {resultTy, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> intersectTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (!packParams.empty())
{
ctx->ice->ice("intersect type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
if (typeParams.size() == 1)
return {follow(typeParams[0]), Reduction::MaybeOk, {}, {}};
std::vector<TypeId> types;
types.reserve(typeParams.size());
for (auto ty : typeParams)
types.emplace_back(follow(ty));
if (types.size() == 2 && get<NoRefineType>(types[1]))
return {types[0], Reduction::MaybeOk, {}, {}};
else if (types.size() == 2 && get<NoRefineType>(types[0]))
return {types[1], Reduction::MaybeOk, {}, {}};
for (auto ty : types)
{
if (isPending(ty, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
else if (get<NeverType>(ty))
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
}
TypeId resultTy = ctx->builtins->unknownType;
DenseHashSet<TypeId> unintersectableTypes{nullptr};
for (auto ty : types)
{
if (get<NoRefineType>(ty))
continue;
if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty))
{
if (get<NeverType>(*simpleResult))
unintersectableTypes.insert(follow(ty));
else
resultTy = *simpleResult;
continue;
}
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (get<NeverType>(result.result))
{
unintersectableTypes.insert(follow(ty));
continue;
}
for (TypeId blockedType : result.blockedTypes)
{
if (!get<GenericType>(blockedType))
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
}
resultTy = result.result;
}
if (!unintersectableTypes.empty())
{
unintersectableTypes.insert(resultTy);
if (unintersectableTypes.size() > 1)
{
TypeId intersection =
ctx->arena->addType(IntersectionType{std::vector<TypeId>(unintersectableTypes.begin(), unintersectableTypes.end())});
return {intersection, Reduction::MaybeOk, {}, {}};
}
else
{
return {*unintersectableTypes.begin(), Reduction::MaybeOk, {}, {}};
}
}
if (get<NeverType>(resultTy))
{
TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
return {intersection, Reduction::MaybeOk, {}, {}};
}
return {resultTy, Reduction::MaybeOk, {}, {}};
}
namespace
{
bool computeKeysOf(TypeId ty, Set<std::optional<std::string>>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx)
{
if (get<PrimitiveType>(ty))
return false;
if (seen.contains(ty))
return true;
seen.insert(ty);
if (auto tableTy = get<TableType>(ty))
{
if (tableTy->indexer)
{
if (isString(tableTy->indexer->indexType))
return false;
}
for (const auto& [key, _] : tableTy->props)
result.insert(key);
return true;
}
if (auto metatableTy = get<MetatableType>(ty))
{
bool res = true;
if (!isRaw)
{
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
if (mmType)
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
}
res = res && computeKeysOf(metatableTy->table, result, seen, isRaw, ctx);
return res;
}
if (auto classTy = get<ExternType>(ty))
{
for (const auto& [key, _] : classTy->props)
result.insert(key);
bool res = true;
if (classTy->metatable && !isRaw)
{
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
if (mmType)
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
}
if (classTy->parent)
res = res && computeKeysOf(follow(*classTy->parent), result, seen, isRaw, ctx);
return res;
}
LUAU_ASSERT(false);
return false;
}
}
TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx,
bool isRaw
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId operandTy = follow(typeParams.at(0));
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (normTy->hasTables() == normTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (normTy->hasTops() || normTy->hasBooleans() || normTy->hasErrors() || normTy->hasNils() || normTy->hasNumbers() || normTy->hasStrings() ||
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
return {std::nullopt, Reduction::Erroneous, {}, {}};
Set<std::optional<std::string>> keys{std::nullopt};
if (normTy->hasExternTypes())
{
LUAU_ASSERT(!normTy->hasTables());
DenseHashSet<TypeId> seen{{}};
auto externTypeIter = normTy->externTypes.ordering.begin();
auto externTypeIterEnd = normTy->externTypes.ordering.end();
LUAU_ASSERT(externTypeIter != externTypeIterEnd);
if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
while (++externTypeIter != externTypeIterEnd)
{
seen.clear();
Set<std::optional<std::string>> localKeys{std::nullopt};
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
{
if (!localKeys.contains(key))
keys.erase(key);
}
}
}
if (normTy->hasTables())
{
LUAU_ASSERT(!normTy->hasExternTypes());
DenseHashSet<TypeId> seen{{}};
auto tablesIter = normTy->tables.begin();
LUAU_ASSERT(tablesIter != normTy->tables.end());
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}};
while (++tablesIter != normTy->tables.end())
{
seen.clear();
Set<std::optional<std::string>> localKeys{std::nullopt};
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
continue;
for (auto& key : keys)
{
if (!localKeys.contains(key))
keys.erase(key);
}
}
}
if (keys.empty())
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
std::vector<TypeId> singletons;
singletons.reserve(keys.size());
for (const auto& key : keys)
{
if (key)
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}}));
}
if (singletons.size() == 1)
return {singletons.front(), Reduction::MaybeOk, {}, {}};
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> keyofTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return keyofFunctionImpl(typeParams, packParams, ctx, false);
}
TypeFunctionReductionResult<TypeId> rawkeyofTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("rawkeyof type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return keyofFunctionImpl(typeParams, packParams, ctx, true);
}
bool searchPropsAndIndexer(
TypeId ty,
TableType::Props tblProps,
std::optional<TableIndexer> tblIndexer,
DenseHashSet<TypeId>& result,
NotNull<TypeFunctionContext> ctx
)
{
ty = follow(ty);
if (auto stringSingleton = get<StringSingleton>(get<SingletonType>(ty)))
{
if (tblProps.find(stringSingleton->value) != tblProps.end())
{
Property& prop = tblProps.at(stringSingleton->value);
TypeId propTy;
if (prop.readTy)
propTy = follow(*prop.readTy);
else if (prop.writeTy)
propTy = follow(*prop.writeTy);
else
return false;
if (auto propUnionTy = get<UnionType>(propTy))
{
for (TypeId option : propUnionTy->options)
{
result.insert(follow(option));
}
}
else
result.insert(propTy);
return true;
}
}
if (tblIndexer)
{
TypeId indexType = follow(tblIndexer->indexType);
if (auto tfit = get<TypeFunctionInstanceType>(indexType))
{
if (tfit->function.get() == &ctx->builtins->typeFunctions->indexFunc)
indexType = follow(tblIndexer->indexResultType);
}
if (FFlag::LuauThreadUniferStateThroughTypeFunctionReduction)
{
if (isSubtype(ty, indexType, ctx->arena, ctx->builtins, ctx->scope, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice))
{
TypeId idxResultTy = follow(tblIndexer->indexResultType);
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
{
for (TypeId option : idxResUnionTy->options)
{
result.insert(follow(option));
}
}
else
result.insert(idxResultTy);
return true;
}
}
else
{
if (isSubtype_DEPRECATED(ty, indexType, ctx->scope, ctx->builtins, *ctx->ice, SolverMode::New))
{
TypeId idxResultTy = follow(tblIndexer->indexResultType);
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
{
for (TypeId option : idxResUnionTy->options)
{
result.insert(follow(option));
}
}
else
result.insert(idxResultTy);
return true;
}
}
}
return false;
}
bool tblIndexInto(
TypeId indexer,
TypeId indexee,
DenseHashSet<TypeId>& result,
DenseHashSet<TypeId>& seenSet,
NotNull<TypeFunctionContext> ctx,
bool isRaw
)
{
indexer = follow(indexer);
indexee = follow(indexee);
if (seenSet.contains(indexee))
return false;
seenSet.insert(indexee);
if (auto unionTy = get<UnionType>(indexee))
{
bool res = true;
for (auto component : unionTy)
{
if (seenSet.contains(component) && component != indexee)
continue;
res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw);
}
return res;
}
if (get<FunctionType>(indexee))
{
TypePackId argPack = ctx->arena->addTypePack({indexer});
std::optional<TypePackId> retPack = solveFunctionCall(ctx, ctx->scope->location, indexee, argPack);
if (!retPack.has_value())
return false;
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *retPack, 1);
if (extracted.head.empty())
return false;
result.insert(follow(extracted.head.front()));
return true;
}
if (auto tableTy = get<TableType>(indexee))
{
return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx);
}
if (auto metatableTy = get<MetatableType>(indexee))
{
if (auto tableTy = get<TableType>(follow(metatableTy->table)))
{
if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx))
return true;
}
if (!isRaw)
{
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
if (mmType)
return tblIndexInto(indexer, *mmType, result, seenSet, ctx, isRaw);
}
}
return false;
}
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
{
DenseHashSet<TypeId> seenSet{{}};
return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw);
}
TypeFunctionReductionResult<TypeId> indexFunctionImpl(
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx,
bool isRaw
)
{
TypeId indexeeTy = follow(typeParams.at(0));
if (isPending(indexeeTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
if (!indexeeNormTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (indexeeNormTy->shouldSuppressErrors())
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
return {std::nullopt, Reduction::Erroneous, {}, {}};
TypeId indexerTy = follow(typeParams.at(1));
if (isPending(indexerTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {indexerTy}, {}};
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
if (!indexerNormTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (indexerNormTy->hasTops() || indexerNormTy->hasErrors())
return {std::nullopt, Reduction::Erroneous, {}, {}};
const std::vector<TypeId>* typesToFind = nullptr;
const std::vector<TypeId> singleType{indexerTy};
if (auto unionTy = get<UnionType>(indexerTy))
typesToFind = &unionTy->options;
else
typesToFind = &singleType;
DenseHashSet<TypeId> properties{{}};
if (indexeeNormTy->hasExternTypes())
{
LUAU_ASSERT(!indexeeNormTy->hasTables());
if (isRaw)
return {std::nullopt, Reduction::Erroneous, {}, {}};
for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end();
++externTypeIter)
{
auto externTy = get<ExternType>(*externTypeIter);
if (!externTy)
{
LUAU_ASSERT(false);
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
for (TypeId ty : *typesToFind)
{
if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx))
continue;
auto parent = externTy->parent;
bool foundInParent = false;
while (parent && !foundInParent)
{
auto parentExternType = get<ExternType>(follow(*parent));
foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx);
parent = parentExternType->parent;
}
if (foundInParent)
continue;
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{});
if (!mmType)
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (!tblIndexInto(ty, *mmType, properties, ctx, isRaw))
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
}
}
if (indexeeNormTy->hasTables())
{
LUAU_ASSERT(!indexeeNormTy->hasExternTypes());
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
{
for (TypeId ty : *typesToFind)
if (!tblIndexInto(ty, *tablesIter, properties, ctx, isRaw))
{
if (isRaw)
properties.insert(ctx->builtins->nilType);
else
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
}
}
if (properties.size() == 1)
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
return {ctx->arena->addType(UnionType{std::vector<TypeId>(properties.begin(), properties.end())}), Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> indexTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("index type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return indexFunctionImpl(typeParams, packParams, ctx, false);
}
TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("rawget type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
return indexFunctionImpl(typeParams, packParams, ctx, true);
}
TypeFunctionReductionResult<TypeId> setmetatableTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 2 || !packParams.empty())
{
ctx->ice->ice("setmetatable type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
TypeId targetTy = follow(typeParams.at(0));
TypeId metatableTy = follow(typeParams.at(1));
if (isPending(targetTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
std::shared_ptr<const NormalizedType> targetNorm = ctx->normalizer->normalize(targetTy);
if (!targetNorm)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (!targetNorm->hasTables())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() ||
targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() ||
targetNorm->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (isPending(metatableTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {metatableTy}, {}};
if (!get<TableType>(metatableTy) && !get<MetatableType>(metatableTy))
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (targetNorm->tables.size() == 1)
{
TypeId table = *targetNorm->tables.begin();
ErrorVec dummy;
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, table, "__metatable", location);
if (metatableMetamethod)
return {std::nullopt, Reduction::Erroneous, {}, {}};
TypeId withMetatable = ctx->arena->addType(MetatableType{table, metatableTy});
return {withMetatable, Reduction::MaybeOk, {}, {}};
}
TypeId result = ctx->builtins->neverType;
for (auto componentTy : targetNorm->tables)
{
ErrorVec dummy;
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, componentTy, "__metatable", location);
if (metatableMetamethod)
return {std::nullopt, Reduction::Erroneous, {}, {}};
TypeId withMetatable = ctx->arena->addType(MetatableType{componentTy, metatableTy});
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, result, withMetatable);
if (!simplified.blockedTypes.empty())
{
std::vector<TypeId> blockedTypes{};
blockedTypes.reserve(simplified.blockedTypes.size());
for (auto ty : simplified.blockedTypes)
blockedTypes.push_back(ty);
return {std::nullopt, Reduction::MaybeOk, std::move(blockedTypes), {}};
}
result = simplified.result;
}
return {result, Reduction::MaybeOk, {}, {}};
}
static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, const Location& location, NotNull<TypeFunctionContext> ctx)
{
targetTy = follow(targetTy);
std::optional<TypeId> result = std::nullopt;
bool erroneous = true;
if (get<TableType>(targetTy))
erroneous = false;
if (auto mt = get<MetatableType>(targetTy))
{
result = mt->metatable;
erroneous = false;
}
if (auto clazz = get<ExternType>(targetTy))
{
result = clazz->metatable;
erroneous = false;
}
if (auto primitive = get<PrimitiveType>(targetTy))
{
if (primitive->type == PrimitiveType::Table)
{
result = ctx->arena->addType(UnionType{{ctx->builtins->tableType, ctx->builtins->nilType}});
}
else
{
result = primitive->metatable;
}
erroneous = false;
}
if (auto singleton = get<SingletonType>(targetTy))
{
if (get<StringSingleton>(singleton))
{
auto primitiveString = get<PrimitiveType>(ctx->builtins->stringType);
result = primitiveString->metatable;
}
erroneous = false;
}
if (get<AnyType>(targetTy))
{
result = targetTy;
erroneous = false;
}
if (get<ErrorType>(targetTy))
{
result = targetTy;
erroneous = false;
}
if (erroneous)
return {std::nullopt, Reduction::Erroneous, {}, {}};
ErrorVec dummy;
std::optional<TypeId> metatableMetamethod = findMetatableEntry(ctx->builtins, dummy, targetTy, "__metatable", location);
if (metatableMetamethod)
return {metatableMetamethod, Reduction::MaybeOk, {}, {}};
if (result)
return {result, Reduction::MaybeOk, {}, {}};
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
}
TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("getmetatable type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
TypeId targetTy = follow(typeParams.at(0));
if (isPending(targetTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
if (auto ut = get<UnionType>(targetTy))
{
std::vector<TypeId> options{};
options.reserve(ut->options.size());
for (auto option : ut->options)
{
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(option, location, ctx);
if (!result.result)
return result;
options.push_back(*result.result);
}
return {ctx->arena->addType(UnionType{std::move(options)}), Reduction::MaybeOk, {}, {}};
}
if (auto it = get<IntersectionType>(targetTy))
{
std::vector<TypeId> parts{};
parts.reserve(it->parts.size());
bool erroredWithUnknown = false;
for (auto part : it->parts)
{
TypeFunctionReductionResult<TypeId> result = getmetatableHelper(part, location, ctx);
if (!result.result)
{
if (get<UnknownType>(follow(part)))
{
erroredWithUnknown = true;
continue;
}
else
return result;
}
parts.push_back(*result.result);
}
if (erroredWithUnknown && parts.empty())
return {std::nullopt, Reduction::Erroneous, {}, {}};
if (parts.size() == 1)
return {parts.front(), Reduction::MaybeOk, {}, {}};
return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}};
}
return getmetatableHelper(targetTy, location, ctx);
}
TypeFunctionReductionResult<TypeId> weakoptionalTypeFunc(
TypeId instance,
const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams,
NotNull<TypeFunctionContext> ctx
)
{
if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("weakoptional type function: encountered a type function instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId targetTy = follow(typeParams.at(0));
if (isPending(targetTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {targetTy}, {}};
if (is<NeverType>(instance))
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
std::shared_ptr<const NormalizedType> targetNorm = ctx->normalizer->normalize(targetTy);
if (!targetNorm)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
auto result = ctx->normalizer->isInhabited(targetNorm.get());
if (result == NormalizationResult::False)
return {ctx->builtins->nilType, Reduction::MaybeOk, {}, {}};
return {targetTy, Reduction::MaybeOk, {}, {}};
}
BuiltinTypeFunctions::BuiltinTypeFunctions()
: userFunc{"user", userDefinedTypeFunction}
, notFunc{"not", notTypeFunction}
, lenFunc{"len", lenTypeFunction}
, unmFunc{"unm", unmTypeFunction}
, addFunc{"add", addTypeFunction}
, subFunc{"sub", subTypeFunction}
, mulFunc{"mul", mulTypeFunction}
, divFunc{"div", divTypeFunction}
, idivFunc{"idiv", idivTypeFunction}
, powFunc{"pow", powTypeFunction}
, modFunc{"mod", modTypeFunction}
, concatFunc{"concat", concatTypeFunction}
, andFunc{"and", andTypeFunction, true}
, orFunc{"or", orTypeFunction, true}
, ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction}
, refineFunc{"refine", refineTypeFunction, true}
, singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction}
, keyofFunc{"keyof", keyofTypeFunction}
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
, indexFunc{"index", indexTypeFunction}
, rawgetFunc{"rawget", rawgetTypeFunction}
, setmetatableFunc{"setmetatable", setmetatableTypeFunction}
, getmetatableFunc{"getmetatable", getmetatableTypeFunction}
, weakoptionalFunc{"weakoptional", weakoptionalTypeFunc}
{
}
void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const
{
auto mkUnaryTypeFunction = [&](const TypeFunction* tf)
{
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
GenericTypeDefinition genericT{t};
return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})};
};
auto mkBinaryTypeFunctionWithDefault = [&](const TypeFunction* tf)
{
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
GenericTypeDefinition genericT{t};
GenericTypeDefinition genericU{u, {t}};
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
};
auto mkBinaryTypeFunction = [&](const TypeFunction* tf)
{
TypeId t = arena->addType(GenericType{"T", Polarity::Negative});
TypeId u = arena->addType(GenericType{"U", Polarity::Negative});
GenericTypeDefinition genericT{t};
GenericTypeDefinition genericU{u};
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
};
scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc);
scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc);
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunctionWithDefault(&addFunc);
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunctionWithDefault(&subFunc);
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunctionWithDefault(&mulFunc);
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunctionWithDefault(&divFunc);
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunctionWithDefault(&idivFunc);
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunctionWithDefault(&powFunc);
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunctionWithDefault(&modFunc);
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunctionWithDefault(&concatFunc);
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(<Func);
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc);
scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc);
scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc);
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
}
}