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"
|
2024-02-23 20:08:34 +00:00
|
|
|
#include "Luau/NotNull.h"
|
2024-03-09 00:47:53 +00:00
|
|
|
#include "Luau/OverloadResolution.h"
|
|
|
|
#include "Luau/Set.h"
|
2023-10-06 20:02:32 +01:00
|
|
|
#include "Luau/Simplify.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"
|
2024-03-30 23:14:44 +00:00
|
|
|
#include "Luau/TypeFamilyReductionGuesser.h"
|
2024-03-01 18:45:26 +00:00
|
|
|
#include "Luau/TypeFwd.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"
|
2023-08-04 20:18:54 +01:00
|
|
|
#include "Luau/VisitType.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
#include <iterator>
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
// used to control emitting CodeTooComplex warnings on type family reduction
|
2023-05-12 18:50:47 +01:00
|
|
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
// used to control the limits of type family application over union type arguments
|
|
|
|
// e.g. `mul<a | b, c | d>` blows up into `mul<a, c> | mul<a, d> | mul<b, c> | mul<b, d>`
|
|
|
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'000);
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
// used to control falling back to a more conservative reduction based on guessing
|
|
|
|
// when this value is set to a negative value, guessing will be totally disabled.
|
|
|
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
|
|
|
|
|
2024-05-26 18:09:09 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false);
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
namespace Luau
|
|
|
|
{
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
using TypeOrTypePackIdSet = DenseHashSet<const void*>;
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
struct InstanceCollector : TypeOnceVisitor
|
|
|
|
{
|
2024-01-12 22:25:27 +00:00
|
|
|
VecDeque<TypeId> tys;
|
|
|
|
VecDeque<TypePackId> tps;
|
2024-03-30 23:14:44 +00:00
|
|
|
TypeOrTypePackIdSet shouldGuess{nullptr};
|
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.
|
2024-03-30 23:14:44 +00:00
|
|
|
|
|
|
|
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
|
|
|
|
shouldGuess.insert(ty);
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
tys.push_front(ty);
|
2024-03-30 23:14:44 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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.
|
2024-03-30 23:14:44 +00:00
|
|
|
|
|
|
|
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
|
|
|
|
shouldGuess.insert(tp);
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
tps.push_front(tp);
|
2024-03-30 23:14:44 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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-03-30 23:14:44 +00:00
|
|
|
TypeOrTypePackIdSet shouldGuess;
|
2024-02-16 02:04:39 +00:00
|
|
|
std::vector<TypeId> cyclicTypeFamilies;
|
2024-03-30 23:14:44 +00:00
|
|
|
TypeOrTypePackIdSet irreducible{nullptr};
|
2023-05-12 18:50:47 +01:00
|
|
|
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-03-30 23:14:44 +00:00
|
|
|
FamilyReducer(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, 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-03-30 23:14:44 +00:00
|
|
|
, shouldGuess(std::move(shouldGuess))
|
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)
|
|
|
|
{
|
2024-03-22 17:47:10 +00:00
|
|
|
if (subject->owningArena != ctx.arena.get())
|
|
|
|
ctx.ice->ice("Attempting to modify a type family instance from another arena", location);
|
|
|
|
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-30 23:14:44 +00:00
|
|
|
printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str());
|
|
|
|
|
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)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-22 17:47:10 +00:00
|
|
|
printf("%s is uninhabited\n", toString(subject, {true}).c_str());
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-04-19 22:48:02 +01:00
|
|
|
printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(),
|
|
|
|
reduction.blockedPacks.size());
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-04-19 22:48:02 +01:00
|
|
|
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
irreducible.insert(subject);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (skip == SkipTestResult::Defer)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-04-19 22:48:02 +01:00
|
|
|
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-04-19 22:48:02 +01:00
|
|
|
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
irreducible.insert(subject);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (skip == SkipTestResult::Defer)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-04-19 22:48:02 +01:00
|
|
|
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
template<typename TID>
|
|
|
|
inline bool tryGuessing(TID subject)
|
|
|
|
{
|
|
|
|
if (shouldGuess.contains(subject))
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-30 23:14:44 +00:00
|
|
|
printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str());
|
|
|
|
|
|
|
|
TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer};
|
|
|
|
auto guessed = guesser.guess(subject);
|
|
|
|
|
|
|
|
if (guessed)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-30 23:14:44 +00:00
|
|
|
printf("Selected %s as the guessed result type.\n", toString(*guessed, {true}).c_str());
|
|
|
|
|
|
|
|
replace(subject, *guessed);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-30 23:14:44 +00:00
|
|
|
printf("Failed to produce a guess for the result of %s.\n", toString(subject, {true}).c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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;
|
|
|
|
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-22 17:47:10 +00:00
|
|
|
printf("Trying to reduce %s\n", toString(subject, {true}).c_str());
|
|
|
|
|
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)
|
2024-03-22 17:47:10 +00:00
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-22 17:47:10 +00:00
|
|
|
printf("Irreducible due to irreducible/pending and a non-cyclic family\n");
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
return;
|
2024-03-22 17:47:10 +00:00
|
|
|
}
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2024-03-30 23:14:44 +00:00
|
|
|
if (tryGuessing(subject))
|
|
|
|
return;
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> result =
|
2024-05-31 20:18:18 +01:00
|
|
|
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;
|
|
|
|
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-03-22 17:47:10 +00:00
|
|
|
printf("Trying to reduce %s\n", toString(subject, {true}).c_str());
|
|
|
|
|
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-03-30 23:14:44 +00:00
|
|
|
if (tryGuessing(subject))
|
|
|
|
return;
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
TypeFamilyReductionResult<TypePackId> result =
|
2024-05-31 20:18:18 +01:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess,
|
|
|
|
std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-03-30 23:14:44 +00:00
|
|
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), 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-04-19 22:48:02 +01:00
|
|
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
|
|
|
|
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-04-19 22:48:02 +01:00
|
|
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
|
|
|
|
std::move(collector.cyclicInstance), location, ctx, force);
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
bool isPending(TypeId ty, ConstraintSolver* solver)
|
2024-04-12 18:18:49 +01:00
|
|
|
{
|
2024-06-07 18:51:12 +01:00
|
|
|
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
2024-04-12 18:18:49 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
template<typename F, typename... Args>
|
|
|
|
static std::optional<TypeFamilyReductionResult<TypeId>> tryDistributeTypeFamilyApp(F f, TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, Args&& ...args)
|
2024-04-12 18:18:49 +01:00
|
|
|
{
|
2024-05-31 20:18:18 +01:00
|
|
|
// op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d)
|
|
|
|
bool uninhabited = false;
|
|
|
|
std::vector<TypeId> blockedTypes;
|
|
|
|
std::vector<TypeId> results;
|
|
|
|
size_t cartesianProductSize = 1;
|
2024-04-12 18:18:49 +01:00
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
const UnionType* firstUnion = nullptr;
|
|
|
|
size_t unionIndex;
|
|
|
|
|
|
|
|
std::vector<TypeId> arguments = typeParams;
|
|
|
|
for (size_t i = 0; i < arguments.size(); ++i)
|
|
|
|
{
|
|
|
|
const UnionType* ut = get<UnionType>(follow(arguments[i]));
|
|
|
|
if (!ut)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// We want to find the first union type in the set of arguments to distribute that one and only that one union.
|
|
|
|
// The function `f` we have is recursive, so `arguments[unionIndex]` will be updated in-place for each option in
|
|
|
|
// the union we've found in this context, so that index will no longer be a union type. Any other arguments at
|
|
|
|
// index + 1 or after will instead be distributed, if those are a union, which will be subjected to the same rules.
|
|
|
|
if (!firstUnion && ut)
|
|
|
|
{
|
|
|
|
firstUnion = ut;
|
|
|
|
unionIndex = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
cartesianProductSize *= std::distance(begin(ut), end(ut));
|
|
|
|
|
|
|
|
// TODO: We'd like to report that the type family application is too complex here.
|
|
|
|
if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= cartesianProductSize)
|
|
|
|
return {{std::nullopt, true, {}, {}}};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!firstUnion)
|
|
|
|
{
|
|
|
|
// If we couldn't find any union type argument, we're not distributing.
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (TypeId option : firstUnion)
|
|
|
|
{
|
|
|
|
arguments[unionIndex] = option;
|
|
|
|
|
|
|
|
TypeFamilyReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
|
|
|
|
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
|
|
|
|
uninhabited |= result.uninhabited;
|
|
|
|
|
|
|
|
if (result.uninhabited || !result.result)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
results.push_back(*result.result);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uninhabited || !blockedTypes.empty())
|
|
|
|
return {{std::nullopt, uninhabited, blockedTypes, {}}};
|
|
|
|
|
|
|
|
if (!results.empty())
|
|
|
|
{
|
|
|
|
if (results.size() == 1)
|
|
|
|
return {{results[0], false, {}, {}}};
|
|
|
|
|
|
|
|
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
|
|
|
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
|
|
|
std::move(results),
|
|
|
|
{},
|
|
|
|
});
|
|
|
|
|
|
|
|
return {{resultTy, false, {}, {}}};
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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));
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (ty == instance)
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
if (isPending(ty, ctx->solver))
|
|
|
|
return {std::nullopt, false, {ty}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
// `not` operates on anything and returns a `boolean` always.
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (operandTy == instance)
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
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.
|
2024-06-07 18:51:12 +01:00
|
|
|
if (isPending(operandTy, ctx->solver))
|
2023-10-27 22:18:41 +01:00
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
|
|
|
if (!maybeGeneralized)
|
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
operandTy = *maybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
2024-04-19 22:48:02 +01:00
|
|
|
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
|
2023-10-21 02:10:30 +01:00
|
|
|
|
|
|
|
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
|
2024-04-19 22:48:02 +01:00
|
|
|
if (!normTy || inhabited == NormalizationResult::HitLimits)
|
2023-10-21 02:10:30 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// if we have an uninhabited type (like `never`), we can never observe that the operator didn't work.
|
|
|
|
if (inhabited == NormalizationResult::False)
|
2023-10-21 02:10:30 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
|
2023-10-21 02:10: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;
|
|
|
|
|
|
|
|
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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (operandTy == instance)
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy);
|
|
|
|
if (!maybeGeneralized)
|
|
|
|
return {std::nullopt, false, {operandTy}, {}};
|
|
|
|
operandTy = *maybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
2023-10-21 02:10:30 +01:00
|
|
|
|
|
|
|
// 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, {}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
|
2023-10-21 02:10: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;
|
|
|
|
|
|
|
|
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-03-09 00:47:53 +00:00
|
|
|
NotNull<Constraint> TypeFamilyContext::pushConstraint(ConstraintV&& c)
|
|
|
|
{
|
2024-04-12 18:18:49 +01:00
|
|
|
LUAU_ASSERT(solver);
|
2024-03-09 00:47:53 +00:00
|
|
|
NotNull<Constraint> newConstraint = solver->pushConstraint(scope, constraint ? constraint->location : Location{}, std::move(c));
|
|
|
|
|
|
|
|
// Every constraint that is blocked on the current constraint must also be
|
|
|
|
// blocked on this new one.
|
|
|
|
if (constraint)
|
|
|
|
solver->inheritBlocks(NotNull{constraint}, newConstraint);
|
|
|
|
|
|
|
|
return newConstraint;
|
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-02-16 02:04:39 +00:00
|
|
|
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
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
// isPending of `lhsTy` or `rhsTy` would return true, even if it cycles. We want a different answer for that.
|
|
|
|
if (lhsTy == instance || rhsTy == instance)
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
|
|
|
// if we have a `never`, we can never observe that the math operator is unreachable.
|
|
|
|
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
2024-03-09 00:47:53 +00:00
|
|
|
const Location location = ctx->constraint ? ctx->constraint->location : Location{};
|
|
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
// TODO: Normalization needs to remove cyclic type families from a `NormalizedType`.
|
|
|
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
std::shared_ptr<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're adding two `number` types, the result is `number`.
|
|
|
|
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(numericBinopFamilyFn, instance, typeParams, packParams, ctx, metamethod))
|
|
|
|
return *result;
|
2024-04-12 18:18:49 +01:00
|
|
|
|
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;
|
|
|
|
|
2024-03-09 00:47:53 +00: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
|
|
|
{
|
2024-03-09 00:47:53 +00: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
|
|
|
|
2024-03-09 00:47:53 +00:00
|
|
|
TypePackId argPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
|
|
|
SolveResult solveResult;
|
2023-05-19 20:37:30 +01:00
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
if (!reversed)
|
2024-03-09 00:47:53 +00:00
|
|
|
solveResult = solveFunctionCall(ctx->arena, ctx->builtins, ctx->normalizer, ctx->ice, ctx->limits, ctx->scope, location, *mmType, argPack);
|
2023-10-13 21:20:12 +01:00
|
|
|
else
|
2024-03-09 00:47:53 +00:00
|
|
|
{
|
|
|
|
TypePack* p = getMutable<TypePack>(argPack);
|
|
|
|
std::swap(p->head.front(), p->head.back());
|
|
|
|
solveResult = solveFunctionCall(ctx->arena, ctx->builtins, ctx->normalizer, ctx->ice, ctx->limits, ctx->scope, location, *mmType, argPack);
|
|
|
|
}
|
2023-10-13 21:20:12 +01:00
|
|
|
|
2024-03-09 00:47:53 +00:00
|
|
|
if (!solveResult.typePackId.has_value())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
2023-10-13 21:20:12 +01:00
|
|
|
|
2024-03-09 00:47:53 +00:00
|
|
|
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1);
|
|
|
|
if (extracted.head.empty())
|
2023-05-19 20:37:30 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
2024-03-09 00:47:53 +00:00
|
|
|
|
|
|
|
return {extracted.head.front(), false, {}, {}};
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
// isPending of `lhsTy` or `rhsTy` would return true, even if it cycles. We want a different answer for that.
|
|
|
|
if (lhsTy == instance || rhsTy == instance)
|
|
|
|
return {ctx->builtins->neverType, 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
|
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
std::shared_ptr<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.
|
|
|
|
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, {}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(concatFamilyFn, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
|
2023-10-13 21:20:12 +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;
|
|
|
|
|
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
// 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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2023-10-06 20:02:32 +01:00
|
|
|
// 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-05-31 20:18:18 +01:00
|
|
|
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-02-16 02:04:39 +00:00
|
|
|
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
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (lhsTy == instance || rhsTy == instance)
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
|
2024-03-09 00:47:53 +00:00
|
|
|
if (isPending(lhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (isPending(rhsTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
2024-02-23 20:08:34 +00:00
|
|
|
// Algebra Reduction Rules for comparison family functions
|
|
|
|
// Note that comparing to never tells you nothing about the other operand
|
|
|
|
// lt< 'a , never> -> continue
|
|
|
|
// lt< never, 'a> -> continue
|
|
|
|
// lt< 'a, t> -> 'a is t - we'll solve the constraint, return and solve lt<t, t> -> bool
|
|
|
|
// lt< t, 'a> -> same as above
|
|
|
|
bool canSubmitConstraint = ctx->solver && ctx->constraint;
|
2024-03-01 18:45:26 +00:00
|
|
|
bool lhsFree = get<FreeType>(lhsTy) != nullptr;
|
|
|
|
bool rhsFree = get<FreeType>(rhsTy) != nullptr;
|
2024-02-23 20:08:34 +00:00
|
|
|
if (canSubmitConstraint)
|
|
|
|
{
|
2024-03-01 18:45:26 +00:00
|
|
|
// Implement injective type families for comparison type families
|
|
|
|
// lt <number, t> implies t is number
|
|
|
|
// lt <t, number> implies t is number
|
|
|
|
if (lhsFree && isNumber(rhsTy))
|
2024-04-25 23:26:09 +01:00
|
|
|
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
|
2024-03-01 18:45:26 +00:00
|
|
|
else if (rhsFree && isNumber(lhsTy))
|
2024-04-25 23:26:09 +01:00
|
|
|
emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType);
|
|
|
|
else if (lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False)
|
2024-02-23 20:08:34 +00:00
|
|
|
{
|
2024-03-09 00:47:53 +00:00
|
|
|
auto c1 = ctx->pushConstraint(EqualityConstraint{lhsTy, rhsTy});
|
2024-02-23 20:08:34 +00:00
|
|
|
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
|
|
|
|
}
|
2024-04-25 23:26:09 +01:00
|
|
|
else if (rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False)
|
2024-02-23 20:08:34 +00:00
|
|
|
{
|
2024-03-09 00:47:53 +00:00
|
|
|
auto c1 = ctx->pushConstraint(EqualityConstraint{rhsTy, lhsTy});
|
2024-02-23 20:08:34 +00:00
|
|
|
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-01 18:45:26 +00:00
|
|
|
// The above might have caused the operand types to be rebound, we need to follow them again
|
|
|
|
lhsTy = follow(lhsTy);
|
|
|
|
rhsTy = follow(rhsTy);
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2023-10-27 22:18:41 +01:00
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
2024-04-19 22:48:02 +01:00
|
|
|
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
|
|
|
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
2024-04-19 22:48:02 +01:00
|
|
|
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work.
|
|
|
|
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
if (auto result = tryDistributeTypeFamilyApp(comparisonFamilyFn, instance, typeParams, packParams, ctx, metamethod))
|
|
|
|
return *result;
|
|
|
|
|
2023-10-13 21:20:12 +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;
|
|
|
|
|
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> lhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, lhsTy);
|
|
|
|
std::optional<TypeId> rhsMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, rhsTy);
|
|
|
|
|
|
|
|
if (!lhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {lhsTy}, {}};
|
|
|
|
else if (!rhsMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {rhsTy}, {}};
|
|
|
|
|
|
|
|
lhsTy = *lhsMaybeGeneralized;
|
|
|
|
rhsTy = *rhsMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
|
|
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
2024-04-19 22:48:02 +01:00
|
|
|
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
|
|
|
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
2024-04-19 22:48:02 +01:00
|
|
|
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
|
2023-10-13 21:20:12 +01:00
|
|
|
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.
|
2024-04-19 22:48:02 +01:00
|
|
|
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
|
2023-10-13 21:20:12 +01:00
|
|
|
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!
|
2024-04-05 21:45:09 +01:00
|
|
|
NormalizationResult intersectInhabited = ctx->normalizer->isIntersectionInhabited(lhsTy, rhsTy);
|
2024-05-10 19:21:45 +01:00
|
|
|
if (!mmType)
|
|
|
|
{
|
|
|
|
if (intersectInhabited == NormalizationResult::True)
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}}; // if it's inhabited, everything is okay!
|
|
|
|
|
|
|
|
// we might be in a case where we still want to accept the comparison...
|
|
|
|
if (intersectInhabited == NormalizationResult::False)
|
|
|
|
{
|
|
|
|
// if they're both subtypes of `string` but have no common intersection, the comparison is allowed but always `false`.
|
|
|
|
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
|
|
|
|
return {ctx->builtins->falseType, false, {}, {}};
|
|
|
|
|
|
|
|
// if they're both subtypes of `boolean` but have no common intersection, the comparison is allowed but always `false`.
|
|
|
|
if (normLhsTy->isSubtypeOfBooleans() && normRhsTy->isSubtypeOfBooleans())
|
|
|
|
return {ctx->builtins->falseType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2023-10-13 21:20:12 +01:00
|
|
|
return {std::nullopt, true, {}, {}}; // if it's not, then this family is irreducible!
|
2024-05-10 19:21:45 +01:00
|
|
|
}
|
2023-10-13 21:20:12 +01:00
|
|
|
|
|
|
|
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 ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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}, {}};
|
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if either type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, targetTy);
|
|
|
|
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminantTy);
|
|
|
|
|
|
|
|
if (!targetMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {targetTy}, {}};
|
|
|
|
else if (!discriminantMaybeGeneralized)
|
|
|
|
return {std::nullopt, false, {discriminantTy}, {}};
|
|
|
|
|
|
|
|
targetTy = *targetMaybeGeneralized;
|
|
|
|
discriminantTy = *discriminantMaybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2023-12-15 21:29:06 +00:00
|
|
|
// 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}});
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
|
|
|
|
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(targetTy);
|
2023-12-15 21:29:06 +00:00
|
|
|
|
|
|
|
// 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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
2024-04-19 22:48:02 +01:00
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
2024-04-19 22:48:02 +01:00
|
|
|
ctx->ice->ice("singleton type family: encountered a type family instance without the required argument structure");
|
2024-01-27 03:20:56 +00:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
TypeId type = follow(typeParams.at(0));
|
2024-01-27 03:20:56 +00:00
|
|
|
|
|
|
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
2024-04-19 22:48:02 +01:00
|
|
|
if (isPending(type, ctx->solver))
|
|
|
|
return {std::nullopt, false, {type}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-05-10 19:21:45 +01:00
|
|
|
// if the type is free but has only one remaining reference, we can generalize it to its upper bound here.
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, type);
|
|
|
|
if (!maybeGeneralized)
|
|
|
|
return {std::nullopt, false, {type}, {}};
|
|
|
|
type = *maybeGeneralized;
|
|
|
|
}
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
TypeId followed = type;
|
|
|
|
// we want to follow through a negation here as well.
|
|
|
|
if (auto negation = get<NegationType>(followed))
|
2024-05-17 00:02:03 +01:00
|
|
|
followed = follow(negation->ty);
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// if we have a singleton type or `nil`, which is its own singleton type...
|
|
|
|
if (get<SingletonType>(followed) || isNil(followed))
|
|
|
|
return {type, false, {}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// otherwise, we'll return the top type, `unknown`.
|
|
|
|
return {ctx->builtins->unknownType, false, {}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
|
|
|
{
|
|
|
|
if (!packParams.empty())
|
|
|
|
{
|
|
|
|
ctx->ice->ice("union type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we only have one parameter, there's nothing to do.
|
|
|
|
if (typeParams.size() == 1)
|
|
|
|
return {follow(typeParams[0]), false, {}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// we need to follow all of the type parameters.
|
|
|
|
std::vector<TypeId> types;
|
|
|
|
types.reserve(typeParams.size());
|
|
|
|
for (auto ty : typeParams)
|
|
|
|
types.emplace_back(follow(ty));
|
|
|
|
|
|
|
|
// unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type.
|
|
|
|
// this also will early return if _everything_ is `never`, since we already have to check that.
|
|
|
|
std::optional<TypeId> lastType = std::nullopt;
|
|
|
|
for (auto ty : types)
|
|
|
|
{
|
|
|
|
// if we have a previous type and it's not `never` and the current type isn't `never`...
|
|
|
|
if (lastType && !get<NeverType>(lastType) && !get<NeverType>(ty))
|
|
|
|
{
|
|
|
|
// we know we are not taking the short-circuited path.
|
|
|
|
lastType = std::nullopt;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get<NeverType>(ty))
|
|
|
|
continue;
|
|
|
|
lastType = ty;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
|
|
|
|
if (lastType)
|
|
|
|
return {lastType, false, {}, {}};
|
|
|
|
|
|
|
|
// check to see if the operand types are resolved enough, and wait to reduce if not
|
|
|
|
for (auto ty : types)
|
|
|
|
if (isPending(ty, ctx->solver))
|
|
|
|
return {std::nullopt, false, {ty}, {}};
|
|
|
|
|
|
|
|
// fold over the types with `simplifyUnion`
|
|
|
|
TypeId resultTy = ctx->builtins->neverType;
|
|
|
|
for (auto ty : types)
|
|
|
|
{
|
|
|
|
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
|
|
|
if (!result.blockedTypes.empty())
|
|
|
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
|
|
|
|
|
|
|
resultTy = result.result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {resultTy, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
2024-04-19 22:48:02 +01:00
|
|
|
if (!packParams.empty())
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
|
|
|
ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure");
|
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// if we only have one parameter, there's nothing to do.
|
|
|
|
if (typeParams.size() == 1)
|
|
|
|
return {follow(typeParams[0]), false, {}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// we need to follow all of the type parameters.
|
|
|
|
std::vector<TypeId> types;
|
|
|
|
types.reserve(typeParams.size());
|
|
|
|
for (auto ty : typeParams)
|
|
|
|
types.emplace_back(follow(ty));
|
|
|
|
|
|
|
|
// check to see if the operand types are resolved enough, and wait to reduce if not
|
|
|
|
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
|
|
|
|
for (auto ty : types)
|
|
|
|
{
|
|
|
|
if (isPending(ty, ctx->solver))
|
|
|
|
return {std::nullopt, false, {ty}, {}};
|
|
|
|
else if (get<NeverType>(ty))
|
|
|
|
return {ctx->builtins->neverType, false, {}, {}};
|
|
|
|
}
|
2024-01-27 03:20:56 +00:00
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
// fold over the types with `simplifyIntersection`
|
|
|
|
TypeId resultTy = ctx->builtins->unknownType;
|
|
|
|
for (auto ty : types)
|
|
|
|
{
|
|
|
|
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
|
|
|
if (!result.blockedTypes.empty())
|
|
|
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
|
|
|
|
|
|
|
resultTy = result.result;
|
|
|
|
}
|
2024-01-27 03:20:56 +00:00
|
|
|
|
|
|
|
// 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.
|
2024-04-19 22:48:02 +01:00
|
|
|
if (get<NeverType>(resultTy))
|
2024-01-27 03:20:56 +00:00
|
|
|
{
|
2024-04-19 22:48:02 +01:00
|
|
|
TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
|
2024-01-27 03:20:56 +00:00
|
|
|
return {intersection, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-04-19 22:48:02 +01:00
|
|
|
return {resultTy, false, {}, {}};
|
2024-01-27 03:20:56 +00:00
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
2024-01-12 22:25:27 +00:00
|
|
|
|
|
|
|
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
2024-02-23 20:08:34 +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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-05-31 20:18:18 +01:00
|
|
|
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
2024-04-19 22:48:02 +01:00
|
|
|
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-04-19 22:48:02 +01:00
|
|
|
, singletonFamily{"singleton", singletonFamilyFn}
|
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};
|
2024-03-30 23:14:44 +00:00
|
|
|
GenericTypeDefinition genericU{u, {t}};
|
2023-10-06 20:02:32 +01:00
|
|
|
|
|
|
|
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
|