2023-05-12 18:50:47 +01:00
|
|
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
|
|
|
|
|
|
|
#include "Luau/TypeFamily.h"
|
|
|
|
|
2023-09-22 20:12:15 +01:00
|
|
|
#include "Luau/ConstraintSolver.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
#include "Luau/DenseHash.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/Instantiation.h"
|
|
|
|
#include "Luau/Normalize.h"
|
2023-10-06 20:02:32 +01:00
|
|
|
#include "Luau/Simplify.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
#include "Luau/Substitution.h"
|
2023-10-13 21:20:12 +01:00
|
|
|
#include "Luau/Subtyping.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
#include "Luau/ToString.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/TxnLog.h"
|
|
|
|
#include "Luau/TypeCheckLimits.h"
|
2023-05-19 20:37:30 +01:00
|
|
|
#include "Luau/TypeUtils.h"
|
2023-10-06 20:02:32 +01:00
|
|
|
#include "Luau/Unifier2.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/VisitType.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
|
|
|
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
|
|
|
|
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
|
|
|
struct InstanceCollector : TypeOnceVisitor
|
|
|
|
{
|
|
|
|
std::deque<TypeId> tys;
|
|
|
|
std::deque<TypePackId> tps;
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
|
|
|
|
{
|
|
|
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
|
|
|
// cycles. This means that by pushing to the front of the queue, we will
|
|
|
|
// try to reduce deeper instances first if we start with the first thing
|
|
|
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
|
|
|
// we want to reduce the innermost Add<number, number> instantiation
|
|
|
|
// first.
|
|
|
|
tys.push_front(ty);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:37:30 +01:00
|
|
|
bool visit(TypeId ty, const ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override
|
|
|
|
{
|
|
|
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
|
|
|
// cycles. This means that by pushing to the front of the queue, we will
|
|
|
|
// try to reduce deeper instances first if we start with the first thing
|
|
|
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
|
|
|
// we want to reduce the innermost Add<number, number> instantiation
|
|
|
|
// first.
|
|
|
|
tps.push_front(tp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct FamilyReducer
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeFamilyContext ctx;
|
2023-09-22 20:12:15 +01:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
std::deque<TypeId> queuedTys;
|
|
|
|
std::deque<TypePackId> queuedTps;
|
|
|
|
DenseHashSet<const void*> irreducible{nullptr};
|
|
|
|
FamilyGraphReductionResult result;
|
|
|
|
bool force = false;
|
2023-09-22 20:12:15 +01:00
|
|
|
|
|
|
|
// Local to the constraint being reduced.
|
|
|
|
Location location;
|
2023-10-06 20:02:32 +01:00
|
|
|
|
|
|
|
FamilyReducer(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force = false)
|
|
|
|
: ctx(ctx)
|
2023-09-22 20:12:15 +01:00
|
|
|
, queuedTys(std::move(queuedTys))
|
|
|
|
, queuedTps(std::move(queuedTps))
|
2023-05-12 18:50:47 +01:00
|
|
|
, force(force)
|
2023-09-22 20:12:15 +01:00
|
|
|
, location(location)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
enum class SkipTestResult
|
|
|
|
{
|
|
|
|
Irreducible,
|
|
|
|
Defer,
|
|
|
|
Okay,
|
|
|
|
};
|
|
|
|
|
|
|
|
SkipTestResult testForSkippability(TypeId ty)
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
ty = follow(ty);
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
if (is<TypeFamilyInstanceType>(ty))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
if (!irreducible.contains(ty))
|
|
|
|
return SkipTestResult::Defer;
|
|
|
|
else
|
|
|
|
return SkipTestResult::Irreducible;
|
|
|
|
}
|
2023-10-06 20:02:32 +01:00
|
|
|
else if (is<GenericType>(ty))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
return SkipTestResult::Irreducible;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SkipTestResult::Okay;
|
|
|
|
}
|
|
|
|
|
|
|
|
SkipTestResult testForSkippability(TypePackId ty)
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
ty = follow(ty);
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
if (is<TypeFamilyInstanceTypePack>(ty))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
if (!irreducible.contains(ty))
|
|
|
|
return SkipTestResult::Defer;
|
|
|
|
else
|
|
|
|
return SkipTestResult::Irreducible;
|
|
|
|
}
|
2023-10-06 20:02:32 +01:00
|
|
|
else if (is<GenericTypePack>(ty))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
return SkipTestResult::Irreducible;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SkipTestResult::Okay;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
void replace(T subject, T replacement)
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
|
2023-05-12 18:50:47 +01:00
|
|
|
|
|
|
|
if constexpr (std::is_same_v<T, TypeId>)
|
|
|
|
result.reducedTypes.insert(subject);
|
|
|
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
|
|
|
result.reducedPacks.insert(subject);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
void handleFamilyReduction(T subject, TypeFamilyReductionResult<T> reduction)
|
|
|
|
{
|
|
|
|
if (reduction.result)
|
|
|
|
replace(subject, *reduction.result);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
irreducible.insert(subject);
|
|
|
|
|
|
|
|
if (reduction.uninhabited || force)
|
|
|
|
{
|
|
|
|
if constexpr (std::is_same_v<T, TypeId>)
|
|
|
|
result.errors.push_back(TypeError{location, UninhabitedTypeFamily{subject}});
|
|
|
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
|
|
|
result.errors.push_back(TypeError{location, UninhabitedTypePackFamily{subject}});
|
|
|
|
}
|
|
|
|
else if (!reduction.uninhabited && !force)
|
|
|
|
{
|
|
|
|
for (TypeId b : reduction.blockedTypes)
|
|
|
|
result.blockedTypes.insert(b);
|
|
|
|
|
|
|
|
for (TypePackId b : reduction.blockedPacks)
|
|
|
|
result.blockedPacks.insert(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool done()
|
|
|
|
{
|
|
|
|
return queuedTys.empty() && queuedTps.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, typename I>
|
|
|
|
bool testParameters(T subject, const I* tfit)
|
|
|
|
{
|
|
|
|
for (TypeId p : tfit->typeArguments)
|
|
|
|
{
|
|
|
|
SkipTestResult skip = testForSkippability(p);
|
|
|
|
|
|
|
|
if (skip == SkipTestResult::Irreducible)
|
|
|
|
{
|
|
|
|
irreducible.insert(subject);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (skip == SkipTestResult::Defer)
|
|
|
|
{
|
|
|
|
if constexpr (std::is_same_v<T, TypeId>)
|
|
|
|
queuedTys.push_back(subject);
|
|
|
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
|
|
|
queuedTps.push_back(subject);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (TypePackId p : tfit->packArguments)
|
|
|
|
{
|
|
|
|
SkipTestResult skip = testForSkippability(p);
|
|
|
|
|
|
|
|
if (skip == SkipTestResult::Irreducible)
|
|
|
|
{
|
|
|
|
irreducible.insert(subject);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (skip == SkipTestResult::Defer)
|
|
|
|
{
|
|
|
|
if constexpr (std::is_same_v<T, TypeId>)
|
|
|
|
queuedTys.push_back(subject);
|
|
|
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
|
|
|
queuedTps.push_back(subject);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stepType()
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeId subject = follow(queuedTys.front());
|
2023-05-12 18:50:47 +01:00
|
|
|
queuedTys.pop_front();
|
|
|
|
|
|
|
|
if (irreducible.contains(subject))
|
|
|
|
return;
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
if (const TypeFamilyInstanceType* tfit = get<TypeFamilyInstanceType>(subject))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
if (!testParameters(subject, tfit))
|
|
|
|
return;
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
2023-05-12 18:50:47 +01:00
|
|
|
handleFamilyReduction(subject, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void stepPack()
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
TypePackId subject = follow(queuedTps.front());
|
2023-05-12 18:50:47 +01:00
|
|
|
queuedTps.pop_front();
|
|
|
|
|
|
|
|
if (irreducible.contains(subject))
|
|
|
|
return;
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
if (const TypeFamilyInstanceTypePack* tfit = get<TypeFamilyInstanceTypePack>(subject))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
if (!testParameters(subject, tfit))
|
|
|
|
return;
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
2023-05-12 18:50:47 +01:00
|
|
|
handleFamilyReduction(subject, result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void step()
|
|
|
|
{
|
|
|
|
if (!queuedTys.empty())
|
|
|
|
stepType();
|
|
|
|
else if (!queuedTps.empty())
|
|
|
|
stepPack();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
static FamilyGraphReductionResult reduceFamiliesInternal(
|
|
|
|
std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, TypeFamilyContext ctx, bool force)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, ctx, force};
|
2023-05-12 18:50:47 +01:00
|
|
|
int iterationCount = 0;
|
|
|
|
|
|
|
|
while (!reducer.done())
|
|
|
|
{
|
|
|
|
reducer.step();
|
|
|
|
|
|
|
|
++iterationCount;
|
|
|
|
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
|
|
|
{
|
|
|
|
reducer.result.errors.push_back(TypeError{location, CodeTooComplex{}});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::move(reducer.result);
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
2023-09-22 20:12:15 +01:00
|
|
|
{
|
|
|
|
InstanceCollector collector;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
collector.traverse(entrypoint);
|
|
|
|
}
|
|
|
|
catch (RecursionLimitException&)
|
|
|
|
{
|
|
|
|
return FamilyGraphReductionResult{};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (collector.tys.empty() && collector.tps.empty())
|
|
|
|
return {};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force);
|
2023-09-22 20:12:15 +01:00
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
2023-09-22 20:12:15 +01:00
|
|
|
{
|
|
|
|
InstanceCollector collector;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
collector.traverse(entrypoint);
|
|
|
|
}
|
|
|
|
catch (RecursionLimitException&)
|
|
|
|
{
|
|
|
|
return FamilyGraphReductionResult{};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (collector.tys.empty() && collector.tps.empty())
|
|
|
|
return {};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, ctx, force);
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
bool isPending(TypeId ty, ConstraintSolver* solver)
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
return is<BlockedType>(ty) || is<PendingExpansionType>(ty) || is<TypeFamilyInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> notFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("not type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId ty = follow(typeParams.at(0));
|
|
|
|
|
|
|
|
if (isPending(ty, ctx->solver))
|
|
|
|
return {std::nullopt, false, {ty}, {}};
|
|
|
|
|
|
|
|
// `not` operates on anything and returns a `boolean` always.
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2023-10-21 02:10:30 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> lenFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("len type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId operandTy = follow(typeParams.at(0));
|
|
|
|
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
|
|
|
|
|
|
|
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if the operand type is error suppressing, we can immediately reduce to `number`.
|
|
|
|
if (normTy->shouldSuppressErrors())
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the operator didn't work.
|
|
|
|
if (is<NeverType>(operandTy))
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we're checking the length of a string, that works!
|
|
|
|
if (normTy->isSubtypeOfString())
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
|
|
|
// we use the normalized operand here in case there was an intersection or union.
|
|
|
|
TypeId normalizedOperand = ctx->normalizer->typeFromNormal(*normTy);
|
|
|
|
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, we wait to see if the operand type is resolved
|
|
|
|
if (isPending(operandTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
ErrorVec dummy;
|
|
|
|
|
|
|
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
|
|
|
|
if (!mmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
|
|
|
|
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy});
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// `len` must return a `number`.
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> unmFamilyFn(
|
|
|
|
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("unm type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId operandTy = follow(typeParams.at(0));
|
|
|
|
const NormalizedType* normTy = ctx->normalizer->normalize(operandTy);
|
|
|
|
|
|
|
|
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if the operand is error suppressing, we can just go ahead and reduce.
|
|
|
|
if (normTy->shouldSuppressErrors())
|
|
|
|
return {operandTy, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the operation didn't work.
|
|
|
|
if (is<NeverType>(operandTy))
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
// If the type is exactly `number`, we can reduce now.
|
|
|
|
if (normTy->isExactlyNumber())
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, check if we need to wait on the type to be further resolved
|
|
|
|
if (isPending(operandTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
ErrorVec dummy;
|
|
|
|
|
|
|
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__unm", Location{});
|
|
|
|
if (!mmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
|
|
|
|
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy});
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
|
|
|
return {*ret, false, {}, {}};
|
|
|
|
else
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(
|
2023-10-13 21:20:12 +01:00
|
|
|
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
ctx->ice->ice("encountered a type family instance without the required argument structure");
|
2023-05-19 20:37:30 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
TypeId lhsTy = follow(typeParams.at(0));
|
|
|
|
TypeId rhsTy = follow(typeParams.at(1));
|
|
|
|
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
2023-06-02 20:52:15 +01:00
|
|
|
if (!normLhsTy || !normRhsTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
|
|
|
|
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
2023-10-06 20:02:32 +01:00
|
|
|
return {ctx->builtins->anyType, false, {}, {}};
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the numeric operator didn't work.
|
|
|
|
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
2023-10-06 20:02:32 +01:00
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if we're adding two `number` types, the result is `number`.
|
|
|
|
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, check if we need to wait on either type to be further resolved
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
2023-05-19 20:37:30 +01:00
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
2023-10-06 20:02:32 +01:00
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
2023-05-19 20:37:30 +01:00
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
ErrorVec dummy;
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{});
|
2023-05-19 20:37:30 +01:00
|
|
|
bool reversed = false;
|
2023-10-06 20:02:32 +01:00
|
|
|
if (!mmType)
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{});
|
2023-05-19 20:37:30 +01:00
|
|
|
reversed = true;
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
if (!mmType)
|
2023-05-19 20:37:30 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
2023-05-19 20:37:30 +01:00
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
2023-05-19 20:37:30 +01:00
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
2023-05-19 20:37:30 +01:00
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
2023-05-19 20:37:30 +01:00
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
std::vector<TypeId> inferredArgs;
|
|
|
|
if (!reversed)
|
|
|
|
inferredArgs = {lhsTy, rhsTy};
|
|
|
|
else
|
|
|
|
inferredArgs = {rhsTy, lhsTy};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
|
|
|
return {*ret, false, {}, {}};
|
2023-05-19 20:37:30 +01:00
|
|
|
else
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> addFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("add type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__add");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> subFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("sub type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__sub");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> mulFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("mul type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mul");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> divFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("div type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__div");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> idivFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("integer div type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__idiv");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> powFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("pow type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__pow");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> modFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("modulo type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mod");
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> concatFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("concat type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId lhsTy = follow(typeParams.at(0));
|
|
|
|
TypeId rhsTy = follow(typeParams.at(1));
|
|
|
|
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normLhsTy || !normRhsTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
|
|
|
|
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
|
|
|
return {ctx->builtins->anyType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the numeric operator didn't work.
|
|
|
|
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we're concatenating two elements that are either strings or numbers, the result is `string`.
|
|
|
|
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
|
|
|
|
return {ctx->builtins->stringType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, check if we need to wait on either type to be further resolved
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
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, true, {}, {}};
|
|
|
|
|
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
|
|
|
|
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
|
|
|
|
|
|
|
std::vector<TypeId> inferredArgs;
|
|
|
|
if (!reversed)
|
|
|
|
inferredArgs = {lhsTy, rhsTy};
|
|
|
|
else
|
|
|
|
inferredArgs = {rhsTy, lhsTy};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->builtins->stringType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> andFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("and type family: encountered a type family 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, false, {lhsTy}, {}};
|
|
|
|
}
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
{
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is truthy.
|
|
|
|
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(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end());
|
|
|
|
blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end());
|
|
|
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> orFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("or type family: encountered a type family 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, false, {lhsTy}, {}};
|
|
|
|
}
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
{
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
|
|
|
|
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(filteredLhs.blockedTypes.begin(), filteredLhs.blockedTypes.end());
|
|
|
|
blockedTypes.insert(blockedTypes.end(), overallResult.blockedTypes.begin(), overallResult.blockedTypes.end());
|
|
|
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(
|
|
|
|
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId lhsTy = follow(typeParams.at(0));
|
|
|
|
TypeId rhsTy = follow(typeParams.at(1));
|
|
|
|
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normLhsTy || !normRhsTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if one of the types is error suppressing, we can just go ahead and reduce.
|
|
|
|
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the comparison didn't work.
|
|
|
|
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// If both types are some strict subset of `string`, we can reduce now.
|
|
|
|
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// If both types are exactly `number`, we can reduce now.
|
|
|
|
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, check if we need to wait on either type to be further resolved
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
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, true, {}, {}};
|
|
|
|
|
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
|
|
|
|
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> ltFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("lt type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return comparisonFamilyFn(typeParams, packParams, ctx, "__lt");
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> leFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("le type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return comparisonFamilyFn(typeParams, packParams, ctx, "__le");
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("eq type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId lhsTy = follow(typeParams.at(0));
|
|
|
|
TypeId rhsTy = follow(typeParams.at(1));
|
|
|
|
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normLhsTy || !normRhsTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if one of the types is error suppressing, we can just go ahead and reduce.
|
|
|
|
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the comparison didn't work.
|
|
|
|
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
|
|
|
|
// otherwise, check if we need to wait on either type to be further resolved
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
|
|
|
// the necessary state to do that, even if we intend to just eat the errors.
|
|
|
|
ErrorVec dummy;
|
|
|
|
|
|
|
|
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__eq", Location{});
|
|
|
|
if (!mmType)
|
|
|
|
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__eq", Location{});
|
|
|
|
|
|
|
|
// if neither type has a metatable entry for `__eq`, then we'll check for inhabitance of the intersection!
|
|
|
|
if (!mmType && ctx->normalizer->isIntersectionInhabited(lhsTy, rhsTy))
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}}; // if it's inhabited, everything is okay!
|
|
|
|
else if (!mmType)
|
|
|
|
return {std::nullopt, true, {}, {}}; // if it's not, then this family is irreducible!
|
|
|
|
|
|
|
|
mmType = follow(*mmType);
|
|
|
|
if (isPending(*mmType, ctx->solver))
|
|
|
|
return {std::nullopt, false, {*mmType}, {}};
|
|
|
|
|
|
|
|
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
|
|
|
if (!mmFtv)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
|
|
|
if (!instantiatedMmType)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
|
|
|
if (!instantiatedMmFtv)
|
|
|
|
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
|
|
|
|
|
|
|
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
|
|
|
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
|
|
|
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
|
|
|
return {std::nullopt, true, {}, {}}; // occurs check failed
|
|
|
|
|
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:37:30 +01:00
|
|
|
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
2023-10-13 21:20:12 +01:00
|
|
|
: notFamily{"not", notFamilyFn}
|
2023-10-21 02:10:30 +01:00
|
|
|
, lenFamily{"len", lenFamilyFn}
|
|
|
|
, unmFamily{"unm", unmFamilyFn}
|
2023-10-13 21:20:12 +01:00
|
|
|
, addFamily{"add", addFamilyFn}
|
|
|
|
, subFamily{"sub", subFamilyFn}
|
|
|
|
, mulFamily{"mul", mulFamilyFn}
|
|
|
|
, divFamily{"div", divFamilyFn}
|
|
|
|
, idivFamily{"idiv", idivFamilyFn}
|
|
|
|
, powFamily{"pow", powFamilyFn}
|
|
|
|
, modFamily{"mod", modFamilyFn}
|
|
|
|
, concatFamily{"concat", concatFamilyFn}
|
|
|
|
, andFamily{"and", andFamilyFn}
|
|
|
|
, orFamily{"or", orFamilyFn}
|
|
|
|
, ltFamily{"lt", ltFamilyFn}
|
|
|
|
, leFamily{"le", leFamilyFn}
|
|
|
|
, eqFamily{"eq", eqFamilyFn}
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void BuiltinTypeFamilies::addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
2023-10-21 02:10:30 +01:00
|
|
|
// make a type function for a one-argument type family
|
|
|
|
auto mkUnaryTypeFamily = [&](const TypeFamily* family) {
|
|
|
|
TypeId t = arena->addType(GenericType{"T"});
|
|
|
|
GenericTypeDefinition genericT{t};
|
|
|
|
|
|
|
|
return TypeFun{{genericT}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t}, {}})};
|
|
|
|
};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
// make a type function for a two-argument type family
|
|
|
|
auto mkBinaryTypeFamily = [&](const TypeFamily* family) {
|
|
|
|
TypeId t = arena->addType(GenericType{"T"});
|
|
|
|
TypeId u = arena->addType(GenericType{"U"});
|
|
|
|
GenericTypeDefinition genericT{t};
|
|
|
|
GenericTypeDefinition genericU{u};
|
|
|
|
|
|
|
|
return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})};
|
|
|
|
};
|
|
|
|
|
2023-10-21 02:10:30 +01:00
|
|
|
scope->exportedTypeBindings[lenFamily.name] = mkUnaryTypeFamily(&lenFamily);
|
|
|
|
scope->exportedTypeBindings[unmFamily.name] = mkUnaryTypeFamily(&unmFamily);
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
scope->exportedTypeBindings[addFamily.name] = mkBinaryTypeFamily(&addFamily);
|
|
|
|
scope->exportedTypeBindings[subFamily.name] = mkBinaryTypeFamily(&subFamily);
|
|
|
|
scope->exportedTypeBindings[mulFamily.name] = mkBinaryTypeFamily(&mulFamily);
|
|
|
|
scope->exportedTypeBindings[divFamily.name] = mkBinaryTypeFamily(&divFamily);
|
|
|
|
scope->exportedTypeBindings[idivFamily.name] = mkBinaryTypeFamily(&idivFamily);
|
|
|
|
scope->exportedTypeBindings[powFamily.name] = mkBinaryTypeFamily(&powFamily);
|
|
|
|
scope->exportedTypeBindings[modFamily.name] = mkBinaryTypeFamily(&modFamily);
|
2023-10-13 21:20:12 +01:00
|
|
|
scope->exportedTypeBindings[concatFamily.name] = mkBinaryTypeFamily(&concatFamily);
|
|
|
|
|
|
|
|
scope->exportedTypeBindings[ltFamily.name] = mkBinaryTypeFamily(<Family);
|
|
|
|
scope->exportedTypeBindings[leFamily.name] = mkBinaryTypeFamily(&leFamily);
|
|
|
|
scope->exportedTypeBindings[eqFamily.name] = mkBinaryTypeFamily(&eqFamily);
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|