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"
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
#include "Luau/Common.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"
|
2024-02-16 02:04:39 +00:00
|
|
|
#include "Luau/Type.h"
|
2023-08-04 20:18:54 +01:00
|
|
|
#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"
|
2024-01-12 22:25:27 +00:00
|
|
|
#include "Luau/VecDeque.h"
|
2024-02-16 02:04:39 +00:00
|
|
|
#include "Luau/Set.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
|
|
|
|
{
|
2024-01-12 22:25:27 +00:00
|
|
|
VecDeque<TypeId> tys;
|
|
|
|
VecDeque<TypePackId> tps;
|
2024-02-16 02:04:39 +00:00
|
|
|
std::vector<TypeId> cyclicInstance;
|
2023-05-12 18:50:47 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
void cycle(TypeId ty) override
|
|
|
|
{
|
|
|
|
/// Detected cyclic type pack
|
|
|
|
TypeId t = follow(ty);
|
|
|
|
if (get<TypeFamilyInstanceType>(t))
|
|
|
|
cyclicInstance.push_back(t);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2024-01-12 22:25:27 +00:00
|
|
|
VecDeque<TypeId> queuedTys;
|
|
|
|
VecDeque<TypePackId> queuedTps;
|
2024-02-16 02:04:39 +00:00
|
|
|
std::vector<TypeId> cyclicTypeFamilies;
|
2023-05-12 18:50:47 +01:00
|
|
|
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
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
FamilyReducer(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, std::vector<TypeId> cyclicTypes, Location location,
|
|
|
|
TypeFamilyContext ctx, bool force = false)
|
2023-10-06 20:02:32 +01:00
|
|
|
: ctx(ctx)
|
2023-09-22 20:12:15 +01:00
|
|
|
, queuedTys(std::move(queuedTys))
|
|
|
|
, queuedTps(std::move(queuedTps))
|
2024-02-16 02:04:39 +00:00
|
|
|
, cyclicTypeFamilies(std::move(cyclicTypes))
|
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
|
|
|
|
{
|
2024-02-16 02:04:39 +00:00
|
|
|
CyclicTypeFamily,
|
2023-05-12 18:50:47 +01:00
|
|
|
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
|
|
|
{
|
2024-02-16 02:04:39 +00:00
|
|
|
for (auto t : cyclicTypeFamilies)
|
|
|
|
{
|
|
|
|
if (ty == t)
|
|
|
|
return SkipTestResult::CyclicTypeFamily;
|
|
|
|
}
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
if (!irreducible.contains(ty))
|
|
|
|
return SkipTestResult::Defer;
|
2024-02-16 02:04:39 +00:00
|
|
|
|
|
|
|
return SkipTestResult::Irreducible;
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
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
|
|
|
{
|
2024-02-16 02:04:39 +00:00
|
|
|
SkipTestResult testCyclic = testForSkippability(subject);
|
|
|
|
|
|
|
|
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFamily)
|
2023-05-12 18:50:47 +01:00
|
|
|
return;
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(subject, 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;
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(subject, 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(
|
2024-02-16 02:04:39 +00:00
|
|
|
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-02-16 02:04:39 +00:00
|
|
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(cyclics), 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 {};
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.cyclicInstance), 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 {};
|
|
|
|
|
2024-02-16 02:04:39 +00: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
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> notFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> lenFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-21 02:10:30 +01:00
|
|
|
{
|
|
|
|
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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if the operand type is resolved enough, and wait to reduce if not
|
2023-12-15 21:29:06 +00:00
|
|
|
// the use of `typeFromNormal` later necessitates blocking on local types.
|
|
|
|
if (isPending(operandTy, ctx->solver) || get<LocalType>(operandTy))
|
2023-10-27 22:18:41 +01:00
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
|
2023-10-21 02:10:30 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
|
|
|
// 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(
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-21 02:10:30 +01:00
|
|
|
{
|
|
|
|
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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if the operand type is resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(operandTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
|
2023-10-21 02:10:30 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
|
|
|
// 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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, 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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
2023-05-19 20:37:30 +01:00
|
|
|
// 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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> addFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> subFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> mulFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> divFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> idivFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> powFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> modFamilyFn(
|
|
|
|
TypeId instance, 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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> concatFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
|
|
|
// 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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> andFamilyFn(
|
|
|
|
TypeId instance, 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));
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
// t1 = and<lhs, t1> ~> lhs
|
|
|
|
if (follow(rhsTy) == instance && lhsTy != rhsTy)
|
|
|
|
return {lhsTy, false, {}, {}};
|
|
|
|
// t1 = and<t1, rhs> ~> rhs
|
|
|
|
if (follow(lhsTy) == instance && lhsTy != rhsTy)
|
|
|
|
return {rhsTy, false, {}, {}};
|
|
|
|
|
|
|
|
|
2023-10-27 22:18:41 +01:00
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
2023-10-06 20:02:32 +01:00
|
|
|
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);
|
2023-11-10 21:10:07 +00:00
|
|
|
std::vector<TypeId> blockedTypes{};
|
|
|
|
for (auto ty : filteredLhs.blockedTypes)
|
|
|
|
blockedTypes.push_back(ty);
|
|
|
|
for (auto ty : overallResult.blockedTypes)
|
|
|
|
blockedTypes.push_back(ty);
|
2023-10-06 20:02:32 +01:00
|
|
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> orFamilyFn(
|
|
|
|
TypeId instance, 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));
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
// t1 = or<lhs, t1> ~> lhs
|
|
|
|
if (follow(rhsTy) == instance && lhsTy != rhsTy)
|
|
|
|
return {lhsTy, false, {}, {}};
|
|
|
|
// t1 = or<t1, rhs> ~> rhs
|
|
|
|
if (follow(lhsTy) == instance && lhsTy != rhsTy)
|
|
|
|
return {rhsTy, false, {}, {}};
|
|
|
|
|
2023-10-27 22:18:41 +01:00
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
2023-10-06 20:02:32 +01:00
|
|
|
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);
|
2023-11-10 21:10:07 +00:00
|
|
|
std::vector<TypeId> blockedTypes{};
|
|
|
|
for (auto ty : filteredLhs.blockedTypes)
|
|
|
|
blockedTypes.push_back(ty);
|
|
|
|
for (auto ty : overallResult.blockedTypes)
|
|
|
|
blockedTypes.push_back(ty);
|
2023-10-06 20:02:32 +01:00
|
|
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
|
|
|
// 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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> ltFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> leFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> eqFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
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));
|
2023-10-27 22:18:41 +01:00
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
|
|
|
// 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-12-15 21:29:06 +00:00
|
|
|
// Collect types that prevent us from reducing a particular refinement.
|
|
|
|
struct FindRefinementBlockers : TypeOnceVisitor
|
|
|
|
{
|
|
|
|
DenseHashSet<TypeId> found{nullptr};
|
|
|
|
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 LocalType&) override
|
|
|
|
{
|
|
|
|
found.insert(ty);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool visit(TypeId ty, const ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> refineFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2023-12-15 21:29:06 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("refine type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId targetTy = follow(typeParams.at(0));
|
|
|
|
TypeId discriminantTy = follow(typeParams.at(1));
|
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(targetTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {targetTy}, {}};
|
|
|
|
else if (isPending(discriminantTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {discriminantTy}, {}};
|
|
|
|
|
|
|
|
// we need a more complex check for blocking on the discriminant in particular
|
|
|
|
FindRefinementBlockers frb;
|
|
|
|
frb.traverse(discriminantTy);
|
|
|
|
|
|
|
|
if (!frb.found.empty())
|
|
|
|
return {std::nullopt, false, {frb.found.begin(), frb.found.end()}, {}};
|
|
|
|
|
|
|
|
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
|
|
|
|
* that ~any is the same as any. This is so so weird, but refinements needs
|
|
|
|
* some way to say "I may refine this, but I'm not sure."
|
|
|
|
*
|
|
|
|
* It does this by refining on a blocked type and deferring the decision
|
|
|
|
* until it is unblocked.
|
|
|
|
*
|
|
|
|
* Refinements also get negated, so we wind up with types like T & ~*blocked*
|
|
|
|
*
|
|
|
|
* We need to treat T & ~any as T in this case.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (auto nt = get<NegationType>(discriminantTy))
|
|
|
|
if (get<AnyType>(follow(nt->ty)))
|
|
|
|
return {targetTy, false, {}, {}};
|
|
|
|
|
|
|
|
TypeId intersection = ctx->arena->addType(IntersectionType{{targetTy, discriminantTy}});
|
|
|
|
const NormalizedType* normIntersection = ctx->normalizer->normalize(intersection);
|
|
|
|
const NormalizedType* normType = ctx->normalizer->normalize(targetTy);
|
|
|
|
|
|
|
|
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normIntersection || !normType)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
|
|
|
|
|
|
|
|
// include the error type if the target type is error-suppressing and the intersection we computed is not
|
|
|
|
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
|
|
|
|
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
|
|
|
|
|
|
|
return {resultTy, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> unionFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("union 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));
|
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
|
|
|
|
return {rhsTy, false, {}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
|
|
|
|
return {lhsTy, false, {}, {}};
|
|
|
|
|
|
|
|
|
|
|
|
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy);
|
|
|
|
if (!result.blockedTypes.empty())
|
|
|
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
|
|
|
|
|
|
|
return {result.result, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> intersectFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("intersect 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));
|
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy);
|
|
|
|
if (!result.blockedTypes.empty())
|
|
|
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
|
|
|
|
|
|
|
// if the intersection simplifies to `never`, this gives us bad autocomplete.
|
|
|
|
// we'll just produce the intersection plainly instead, but this might be revisitable
|
|
|
|
// if we ever give `never` some kind of "explanation" trail.
|
|
|
|
if (get<NeverType>(result.result))
|
|
|
|
{
|
|
|
|
TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}});
|
|
|
|
return {intersection, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {result.result, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-01-12 22:25:27 +00:00
|
|
|
// computes the keys of `ty` into `result`
|
|
|
|
// `isRaw` parameter indicates whether or not we should follow __index metamethods
|
|
|
|
// returns `false` if `result` should be ignored because the answer is "all strings"
|
2024-01-19 18:04:46 +00:00
|
|
|
bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFamilyContext> ctx)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
// if the type is the top table type, the answer is just "all strings"
|
|
|
|
if (get<PrimitiveType>(ty))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// if we've already seen this type, we can do nothing
|
|
|
|
if (seen.contains(ty))
|
|
|
|
return true;
|
|
|
|
seen.insert(ty);
|
|
|
|
|
|
|
|
// if we have a particular table type, we can insert the keys
|
|
|
|
if (auto tableTy = get<TableType>(ty))
|
|
|
|
{
|
2024-01-19 18:04:46 +00:00
|
|
|
if (tableTy->indexer)
|
|
|
|
{
|
|
|
|
// if we have a string indexer, the answer is, again, "all strings"
|
|
|
|
if (isString(tableTy->indexer->indexType))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-12 22:25:27 +00:00
|
|
|
for (auto [key, _] : tableTy->props)
|
|
|
|
result.insert(key);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise, we have a metatable to deal with
|
|
|
|
if (auto metatableTy = get<MetatableType>(ty))
|
|
|
|
{
|
|
|
|
bool res = true;
|
|
|
|
|
|
|
|
if (!isRaw)
|
|
|
|
{
|
|
|
|
// 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, ty, "__index", Location{});
|
|
|
|
if (mmType)
|
|
|
|
res = res && computeKeysOf(*mmType, result, seen, isRaw, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = res && computeKeysOf(metatableTy->table, result, seen, isRaw, ctx);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this should not be reachable since the type should be a valid tables part from normalization.
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
|
|
|
|
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, bool isRaw)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("keyof 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, {}, {}};
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes as
|
|
|
|
// well)
|
2024-01-12 22:25:27 +00:00
|
|
|
if (normTy->hasTables() == normTy->hasClasses())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
|
|
|
|
if (normTy->hasTops() || normTy->hasBooleans() || normTy->hasErrors() || normTy->hasNils() || normTy->hasNumbers() || normTy->hasStrings() ||
|
|
|
|
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// we're going to collect the keys in here
|
2024-01-19 18:04:46 +00:00
|
|
|
Set<std::string> keys{{}};
|
2024-01-12 22:25:27 +00:00
|
|
|
|
|
|
|
// computing the keys for classes
|
|
|
|
if (normTy->hasClasses())
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!normTy->hasTables());
|
|
|
|
|
|
|
|
auto classesIter = normTy->classes.ordering.begin();
|
|
|
|
auto classesIterEnd = normTy->classes.ordering.end();
|
|
|
|
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check
|
|
|
|
|
|
|
|
auto classTy = get<ClassType>(*classesIter);
|
|
|
|
if (!classTy)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto [key, _] : classTy->props)
|
|
|
|
keys.insert(key);
|
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
// we need to look at each class to remove any keys that are not common amongst them all
|
2024-01-12 22:25:27 +00:00
|
|
|
while (++classesIter != classesIterEnd)
|
|
|
|
{
|
|
|
|
auto classTy = get<ClassType>(*classesIter);
|
|
|
|
if (!classTy)
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(false); // this should not be possible according to normalization's spec
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
for (auto key : keys)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
2024-01-19 18:04:46 +00:00
|
|
|
// remove any keys that are not present in each class
|
|
|
|
if (classTy->props.find(key) == classTy->props.end())
|
|
|
|
keys.erase(key);
|
2024-01-12 22:25:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// computing the keys for tables
|
|
|
|
if (normTy->hasTables())
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!normTy->hasClasses());
|
|
|
|
|
|
|
|
// seen set for key computation for tables
|
|
|
|
DenseHashSet<TypeId> seen{{}};
|
|
|
|
|
|
|
|
auto tablesIter = normTy->tables.begin();
|
|
|
|
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
|
|
|
|
|
|
|
|
// collect all the properties from the first table type
|
|
|
|
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
|
|
|
|
return {ctx->builtins->stringType, false, {}, {}}; // if it failed, we have the top table type!
|
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
// we need to look at each tables to remove any keys that are not common amongst them all
|
2024-01-12 22:25:27 +00:00
|
|
|
while (++tablesIter != normTy->tables.end())
|
|
|
|
{
|
|
|
|
seen.clear(); // we'll reuse the same seen set
|
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
Set<std::string> localKeys{{}};
|
2024-01-12 22:25:27 +00:00
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
// we can skip to the next table if this one is the top table type
|
2024-01-12 22:25:27 +00:00
|
|
|
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
|
2024-01-19 18:04:46 +00:00
|
|
|
continue;
|
2024-01-12 22:25:27 +00:00
|
|
|
|
2024-01-19 18:04:46 +00:00
|
|
|
for (auto key : keys)
|
|
|
|
{
|
|
|
|
// remove any keys that are not present in each table
|
|
|
|
if (!localKeys.contains(key))
|
|
|
|
keys.erase(key);
|
|
|
|
}
|
2024-01-12 22:25:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the set of keys is empty, `keyof<T>` is `never`
|
|
|
|
if (keys.empty())
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
// everything is validated, we need only construct our big union of singletons now!
|
|
|
|
std::vector<TypeId> singletons;
|
|
|
|
singletons.reserve(keys.size());
|
|
|
|
|
|
|
|
for (std::string key : keys)
|
|
|
|
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
|
|
|
|
|
|
|
|
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> keyofFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("keyof type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
|
|
|
}
|
|
|
|
|
2024-02-16 02:04:39 +00:00
|
|
|
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(
|
|
|
|
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("rawkeyof type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
|
|
|
}
|
|
|
|
|
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-12-15 21:29:06 +00:00
|
|
|
, refineFamily{"refine", refineFamilyFn}
|
2024-01-27 03:20:56 +00:00
|
|
|
, unionFamily{"union", unionFamilyFn}
|
|
|
|
, intersectFamily{"intersect", intersectFamilyFn}
|
2024-01-12 22:25:27 +00:00
|
|
|
, keyofFamily{"keyof", keyofFamilyFn}
|
|
|
|
, rawkeyofFamily{"rawkeyof", rawkeyofFamilyFn}
|
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);
|
2024-01-12 22:25:27 +00:00
|
|
|
|
|
|
|
scope->exportedTypeBindings[keyofFamily.name] = mkUnaryTypeFamily(&keyofFamily);
|
|
|
|
scope->exportedTypeBindings[rawkeyofFamily.name] = mkUnaryTypeFamily(&rawkeyofFamily);
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|