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
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
#include "Luau/TypeFunction.h"
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
#include "Luau/BytecodeBuilder.h"
|
2024-02-16 02:04:39 +00:00
|
|
|
#include "Luau/Common.h"
|
2024-09-27 19:58:21 +01:00
|
|
|
#include "Luau/Compiler.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"
|
2024-09-27 19:58:21 +01:00
|
|
|
#include "Luau/TimeTrace.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-07-12 18:03:36 +01:00
|
|
|
#include "Luau/TypeFunctionReductionGuesser.h"
|
2024-09-27 19:58:21 +01:00
|
|
|
#include "Luau/TypeFunctionRuntime.h"
|
|
|
|
#include "Luau/TypeFunctionRuntimeBuilder.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-09-27 19:58:21 +01:00
|
|
|
#include "lua.h"
|
|
|
|
#include "lualib.h"
|
|
|
|
|
2024-04-12 18:18:49 +01:00
|
|
|
#include <iterator>
|
2024-09-27 19:58:21 +01:00
|
|
|
#include <memory>
|
|
|
|
#include <unordered_map>
|
2024-04-12 18:18:49 +01:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
// used to control emitting CodeTooComplex warnings on type function reduction
|
2023-05-12 18:50:47 +01:00
|
|
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
// used to control the limits of type function application over union type arguments
|
2024-04-12 18:18:49 +01:00
|
|
|
// 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-09-27 19:58:21 +01:00
|
|
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies, false)
|
|
|
|
LUAU_FASTFLAGVARIABLE(LuauUserDefinedTypeFunctions, false)
|
2024-03-22 17:47:10 +00:00
|
|
|
|
2024-09-20 17:53:26 +01:00
|
|
|
LUAU_DYNAMIC_FASTINT(LuauTypeSolverRelease)
|
|
|
|
|
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
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
// 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
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
|
2024-03-30 23:14:44 +00:00
|
|
|
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);
|
2024-07-12 18:03:36 +01:00
|
|
|
if (get<TypeFunctionInstanceType>(t))
|
2024-02-16 02:04:39 +00:00
|
|
|
cyclicInstance.push_back(t);
|
|
|
|
}
|
|
|
|
|
2023-05-19 20:37:30 +01:00
|
|
|
bool visit(TypeId ty, const ClassType&) override
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
// 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
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
|
2024-03-30 23:14:44 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
struct TypeFunctionReducer
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
TypeFunctionContext 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-07-19 19:20:47 +01:00
|
|
|
std::vector<TypeId> cyclicTypeFunctions;
|
2024-03-30 23:14:44 +00:00
|
|
|
TypeOrTypePackIdSet irreducible{nullptr};
|
2024-07-12 18:03:36 +01:00
|
|
|
FunctionGraphReductionResult result;
|
2023-05-12 18:50:47 +01:00
|
|
|
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-08-02 15:30:04 +01:00
|
|
|
TypeFunctionReducer(
|
|
|
|
VecDeque<TypeId> queuedTys,
|
|
|
|
VecDeque<TypePackId> queuedTps,
|
|
|
|
TypeOrTypePackIdSet shouldGuess,
|
|
|
|
std::vector<TypeId> cyclicTypes,
|
|
|
|
Location location,
|
|
|
|
TypeFunctionContext 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-07-19 19:20:47 +01:00
|
|
|
, cyclicTypeFunctions(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-07-12 18:03:36 +01:00
|
|
|
CyclicTypeFunction,
|
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
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (is<TypeFunctionInstanceType>(ty))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-07-19 19:20:47 +01:00
|
|
|
for (auto t : cyclicTypeFunctions)
|
2024-02-16 02:04:39 +00:00
|
|
|
{
|
|
|
|
if (ty == t)
|
2024-07-12 18:03:36 +01:00
|
|
|
return SkipTestResult::CyclicTypeFunction;
|
2024-02-16 02:04:39 +00:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
SkipTestResult testForSkippability(TypePackId ty) const
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
ty = follow(ty);
|
2023-05-12 18:50:47 +01:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (is<TypeFunctionInstanceTypePack>(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())
|
2024-06-29 01:34:49 +01:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
result.errors.emplace_back(location, InternalError{"Attempting to modify a type function instance from another arena"});
|
2024-06-29 01:34:49 +01:00
|
|
|
return;
|
|
|
|
}
|
2024-03-22 17:47:10 +00:00
|
|
|
|
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>
|
2024-07-12 18:03:36 +01:00
|
|
|
void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult<T> reduction)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
if (reduction.result)
|
|
|
|
replace(subject, *reduction.result);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
irreducible.insert(subject);
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
if (reduction.error.has_value())
|
|
|
|
result.errors.emplace_back(location, UserDefinedTypeFunctionError{*reduction.error});
|
|
|
|
|
2023-05-12 18:50:47 +01:00
|
|
|
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>)
|
2024-09-27 19:58:21 +01:00
|
|
|
result.errors.emplace_back(location, UninhabitedTypeFunction{subject});
|
2023-05-12 18:50:47 +01:00
|
|
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
2024-09-27 19:58:21 +01:00
|
|
|
result.errors.emplace_back(location, UninhabitedTypePackFunction{subject});
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
else if (!reduction.uninhabited && !force)
|
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-08-02 15:30:04 +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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
bool done() const
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
|
|
|
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());
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
TypeFunctionReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer};
|
2024-03-30 23:14:44 +00:00
|
|
|
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());
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject))
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-02-16 02:04:39 +00:00
|
|
|
SkipTestResult testCyclic = testForSkippability(subject);
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction)
|
2024-03-22 17:47:10 +00:00
|
|
|
{
|
2024-05-26 18:09:09 +01:00
|
|
|
if (FFlag::DebugLuauLogTypeFamilies)
|
2024-07-19 19:20:47 +01:00
|
|
|
printf("Irreducible due to irreducible/pending and a non-cyclic function\n");
|
2024-03-22 17:47:10 +00:00
|
|
|
|
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-08-09 18:18:20 +01:00
|
|
|
ctx.userFuncName = tfit->userFuncName;
|
|
|
|
ctx.userFuncBody = tfit->userFuncBody;
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
2024-07-12 18:03:36 +01:00
|
|
|
handleTypeFunctionReduction(subject, result);
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
if (const TypeFunctionInstanceTypePack* tfit = get<TypeFunctionInstanceTypePack>(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-08-02 15:30:04 +01:00
|
|
|
TypeFunctionReductionResult<TypePackId> result =
|
|
|
|
tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
2024-07-12 18:03:36 +01:00
|
|
|
handleTypeFunctionReduction(subject, result);
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void step()
|
|
|
|
{
|
|
|
|
if (!queuedTys.empty())
|
|
|
|
stepType();
|
|
|
|
else if (!queuedTps.empty())
|
|
|
|
stepPack();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-08-02 15:30:04 +01:00
|
|
|
static FunctionGraphReductionResult reduceFunctionsInternal(
|
|
|
|
VecDeque<TypeId> queuedTys,
|
|
|
|
VecDeque<TypePackId> queuedTps,
|
|
|
|
TypeOrTypePackIdSet shouldGuess,
|
|
|
|
std::vector<TypeId> cyclics,
|
|
|
|
Location location,
|
|
|
|
TypeFunctionContext ctx,
|
|
|
|
bool force
|
|
|
|
)
|
2023-05-12 18:50:47 +01:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
TypeFunctionReducer 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)
|
|
|
|
{
|
2024-09-27 19:58:21 +01:00
|
|
|
reducer.result.errors.emplace_back(location, CodeTooComplex{});
|
2023-05-12 18:50:47 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::move(reducer.result);
|
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
2023-09-22 20:12:15 +01:00
|
|
|
{
|
|
|
|
InstanceCollector collector;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
collector.traverse(entrypoint);
|
|
|
|
}
|
|
|
|
catch (RecursionLimitException&)
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
return FunctionGraphReductionResult{};
|
2023-09-22 20:12:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (collector.tys.empty() && collector.tps.empty())
|
|
|
|
return {};
|
|
|
|
|
2024-08-02 15:30:04 +01:00
|
|
|
return reduceFunctionsInternal(
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force)
|
2023-09-22 20:12:15 +01:00
|
|
|
{
|
|
|
|
InstanceCollector collector;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
collector.traverse(entrypoint);
|
|
|
|
}
|
|
|
|
catch (RecursionLimitException&)
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
return FunctionGraphReductionResult{};
|
2023-09-22 20:12:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (collector.tys.empty() && collector.tps.empty())
|
|
|
|
return {};
|
|
|
|
|
2024-08-02 15:30:04 +01:00
|
|
|
return reduceFunctionsInternal(
|
|
|
|
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-07-12 18:03:36 +01:00
|
|
|
return is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(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>
|
2024-08-02 15:30:04 +01:00
|
|
|
static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunctionApp(
|
|
|
|
F f,
|
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> 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;
|
2024-09-27 19:58:21 +01:00
|
|
|
size_t unionIndex = 0;
|
2024-05-31 20:18:18 +01:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
// TODO: We'd like to report that the type function application is too complex here.
|
2024-05-31 20:18:18 +01:00
|
|
|
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;
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
|
2024-05-31 20:18:18 +01:00
|
|
|
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, {}, {}}};
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
TypeId resultTy = ctx->arena->addType(TypeFunctionInstanceType{
|
|
|
|
NotNull{&builtinTypeFunctions().unionFunc},
|
2024-05-31 20:18:18 +01:00
|
|
|
std::move(results),
|
|
|
|
{},
|
|
|
|
});
|
|
|
|
|
|
|
|
return {{resultTy, false, {}, {}}};
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
2023-05-19 20:37:30 +01:00
|
|
|
}
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
|
|
|
|
|
2024-08-09 18:18:20 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
|
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
|
|
|
{
|
|
|
|
if (!ctx->userFuncName || !ctx->userFuncBody)
|
|
|
|
{
|
|
|
|
ctx->ice->ice("all user-defined type functions must have an associated function definition");
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
for (auto typeParam : typeParams)
|
|
|
|
{
|
|
|
|
TypeId ty = follow(typeParam);
|
|
|
|
|
|
|
|
// block if we need to
|
|
|
|
if (isPending(ty, ctx->solver))
|
|
|
|
return {std::nullopt, false, {ty}, {}};
|
|
|
|
}
|
|
|
|
|
|
|
|
AstName name = *ctx->userFuncName;
|
|
|
|
AstExprFunction* function = *ctx->userFuncBody;
|
|
|
|
|
|
|
|
// Construct ParseResult containing the type function
|
|
|
|
Allocator allocator;
|
|
|
|
AstNameTable names(allocator);
|
|
|
|
|
|
|
|
AstExprGlobal globalName{Location{}, name};
|
|
|
|
AstStatFunction typeFunction{Location{}, &globalName, function};
|
|
|
|
AstStat* stmtArray[] = {&typeFunction};
|
|
|
|
AstArray<AstStat*> stmts{stmtArray, 1};
|
|
|
|
AstStatBlock exec{Location{}, stmts};
|
|
|
|
ParseResult parseResult{&exec, 1};
|
|
|
|
|
|
|
|
BytecodeBuilder builder;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
compileOrThrow(builder, parseResult, names);
|
|
|
|
}
|
|
|
|
catch (CompileError& e)
|
|
|
|
{
|
|
|
|
std::string errMsg = format("'%s' type function failed to compile with error message: %s", name.value, e.what());
|
|
|
|
return {std::nullopt, true, {}, {}, errMsg};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string bytecode = builder.getBytecode();
|
|
|
|
|
|
|
|
// Initialize Lua state
|
|
|
|
StateRef globalState(lua_newstate(typeFunctionAlloc, nullptr), lua_close);
|
|
|
|
lua_State* L = globalState.get();
|
|
|
|
|
|
|
|
lua_setthreaddata(L, ctx.get());
|
|
|
|
|
|
|
|
setTypeFunctionEnvironment(L);
|
|
|
|
|
|
|
|
// Register type userdata
|
|
|
|
registerTypeUserData(L);
|
|
|
|
|
|
|
|
luaL_sandbox(L);
|
|
|
|
luaL_sandboxthread(L);
|
|
|
|
|
|
|
|
// Load bytecode into Luau state
|
|
|
|
if (auto error = checkResultForError(L, name.value, luau_load(L, name.value, bytecode.data(), bytecode.size(), 0)))
|
|
|
|
return {std::nullopt, true, {}, {}, error};
|
|
|
|
|
|
|
|
// Execute the loaded chunk to register the function in the global environment
|
|
|
|
if (auto error = checkResultForError(L, name.value, lua_pcall(L, 0, 0, 0)))
|
|
|
|
return {std::nullopt, true, {}, {}, error};
|
|
|
|
|
|
|
|
// Get type function from the global environment
|
|
|
|
lua_getglobal(L, name.value);
|
|
|
|
if (!lua_isfunction(L, -1))
|
|
|
|
{
|
|
|
|
std::string errMsg = format("Could not find '%s' type function in the global scope", name.value);
|
|
|
|
|
|
|
|
return {std::nullopt, true, {}, {}, errMsg};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push serialized arguments onto the stack
|
|
|
|
|
|
|
|
// Since there aren't any new class types being created in type functions, there isn't a deserialization function
|
|
|
|
// class types. Instead, we can keep this map and return the mapping as the "deserialized value"
|
|
|
|
std::unique_ptr<TypeFunctionRuntimeBuilderState> runtimeBuilder = std::make_unique<TypeFunctionRuntimeBuilderState>(ctx);
|
|
|
|
for (auto typeParam : typeParams)
|
|
|
|
{
|
|
|
|
TypeId ty = follow(typeParam);
|
|
|
|
// This is checked at the top of the function, and should still be true.
|
|
|
|
LUAU_ASSERT(!isPending(ty, ctx->solver));
|
|
|
|
|
|
|
|
TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get());
|
|
|
|
// Check if there were any errors while serializing
|
|
|
|
if (runtimeBuilder->errors.size() != 0)
|
|
|
|
return {std::nullopt, true, {}, {}, runtimeBuilder->errors.front()};
|
|
|
|
|
|
|
|
allocTypeUserData(L, serializedTy->type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up an interrupt handler for type functions to respect type checking limits and LSP cancellation requests.
|
|
|
|
lua_callbacks(L)->interrupt = [](lua_State* L, int gc)
|
|
|
|
{
|
|
|
|
auto ctx = static_cast<const TypeFunctionContext*>(lua_getthreaddata(lua_mainthread(L)));
|
|
|
|
if (ctx->limits->finishTime && TimeTrace::getClock() > *ctx->limits->finishTime)
|
|
|
|
ctx->solver->throwTimeLimitError();
|
|
|
|
|
|
|
|
if (ctx->limits->cancellationToken && ctx->limits->cancellationToken->requested())
|
|
|
|
ctx->solver->throwUserCancelError();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (auto error = checkResultForError(L, name.value, lua_resume(L, nullptr, int(typeParams.size()))))
|
|
|
|
return {std::nullopt, true, {}, {}, error};
|
|
|
|
|
|
|
|
// If the return value is not a type userdata, return with error message
|
|
|
|
if (!isTypeUserData(L, 1))
|
|
|
|
return {std::nullopt, true, {}, {}, format("'%s' type function: returned a non-type value", name.value)};
|
|
|
|
|
|
|
|
TypeFunctionTypeId retTypeFunctionTypeId = getTypeUserData(L, 1);
|
2024-08-09 18:18:20 +01:00
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
// No errors should be present here since we should've returned already if any were raised during serialization.
|
|
|
|
LUAU_ASSERT(runtimeBuilder->errors.size() == 0);
|
|
|
|
|
|
|
|
TypeId retTypeId = deserialize(retTypeFunctionTypeId, runtimeBuilder.get());
|
|
|
|
|
|
|
|
// At least 1 error occured while deserializing
|
|
|
|
if (runtimeBuilder->errors.size() > 0)
|
|
|
|
return {std::nullopt, true, {}, {}, runtimeBuilder->errors.front()};
|
|
|
|
|
|
|
|
return {retTypeId, false, {}, {}};
|
2024-08-09 18:18:20 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> notTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("not type function: encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
2024-05-31 20:18:18 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-21 02:10:30 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("len type function: encountered a type function instance without the required argument structure");
|
2023-10-21 02:10:30 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-06-21 00:37:55 +01:00
|
|
|
std::optional<TypeId> maybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, operandTy, /* avoidSealingTables */ true);
|
2024-05-10 19:21:45 +01:00
|
|
|
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-08-16 19:29:33 +01:00
|
|
|
// # always returns a number, even if its operand is never.
|
2023-10-21 02:10:30 +01:00
|
|
|
// if we're checking the length of a string, that works!
|
2024-08-16 19:29:33 +01:00
|
|
|
if (inhabited == NormalizationResult::False || normTy->isSubtypeOfString())
|
2023-10-21 02:10:30 +01:00
|
|
|
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-09-20 17:53:26 +01:00
|
|
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
|
|
|
{
|
|
|
|
if (auto result = tryDistributeTypeFunctionApp(lenTypeFunction, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
}
|
2024-05-31 20:18:18 +01:00
|
|
|
|
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
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
2024-08-23 17:35:30 +01:00
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
|
2023-10-21 02:10:30 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// `len` must return a `number`.
|
|
|
|
return {ctx->builtins->numberType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-21 02:10:30 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("unm type function: encountered a type function instance without the required argument structure");
|
2023-10-21 02:10:30 +01:00
|
|
|
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-09-20 17:53:26 +01:00
|
|
|
if (DFInt::LuauTypeSolverRelease >= 644)
|
|
|
|
{
|
|
|
|
if (auto result = tryDistributeTypeFunctionApp(unmTypeFunction, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
|
|
|
|
return *result;
|
|
|
|
}
|
2024-05-31 20:18:18 +01:00
|
|
|
|
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
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
2024-08-23 17:35:30 +01:00
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
|
2023-10-21 02:10:30 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
|
|
|
return {*ret, false, {}, {}};
|
|
|
|
else
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
TypeFunctionContext::TypeFunctionContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
|
|
|
|
: arena(cs->arena)
|
|
|
|
, builtins(cs->builtinTypes)
|
|
|
|
, scope(scope)
|
|
|
|
, normalizer(cs->normalizer)
|
|
|
|
, typeFunctionRuntime(cs->typeFunctionRuntime)
|
|
|
|
, ice(NotNull{&cs->iceReporter})
|
|
|
|
, limits(NotNull{&cs->limits})
|
|
|
|
, solver(cs.get())
|
|
|
|
, constraint(constraint.get())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
NotNull<Constraint> TypeFunctionContext::pushConstraint(ConstraintV&& c) const
|
2024-03-09 00:47:53 +00:00
|
|
|
{
|
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-08-02 15:30:04 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(
|
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx,
|
|
|
|
const std::string metamethod
|
|
|
|
)
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("encountered a type function 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-07-19 19:20:47 +01:00
|
|
|
// TODO: Normalization needs to remove cyclic type functions from a `NormalizedType`.
|
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.
|
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-07-19 19:20:47 +01:00
|
|
|
if (auto result = tryDistributeTypeFunctionApp(numericBinopTypeFunction, instance, typeParams, packParams, ctx, metamethod))
|
2024-05-31 20:18:18 +01:00
|
|
|
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-09-27 19:58:21 +01:00
|
|
|
solveResult = solveFunctionCall(
|
|
|
|
ctx->arena, ctx->builtins, ctx->normalizer, ctx->typeFunctionRuntime, 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());
|
2024-09-27 19:58:21 +01:00
|
|
|
solveResult = solveFunctionCall(
|
|
|
|
ctx->arena, ctx->builtins, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice, ctx->limits, ctx->scope, location, *mmType, argPack
|
|
|
|
);
|
2024-03-09 00:47:53 +00:00
|
|
|
}
|
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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> addTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("add type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__add");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> subTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("sub type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__sub");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> mulTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("mul type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mul");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> divTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("div type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__div");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> idivTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("integer div type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__idiv");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> powTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("pow type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__pow");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> modTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("modulo type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mod");
|
2023-10-06 20:02:32 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("concat type function: encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
if (auto result = tryDistributeTypeFunctionApp(concatTypeFunction, instance, typeParams, packParams, ctx))
|
2024-05-31 20:18:18 +01:00
|
|
|
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
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
2024-08-23 17:35:30 +01:00
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
|
2023-10-13 21:20:12 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->builtins->stringType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> andTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("and type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> orTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("or type function: encountered a type function instance without the required argument structure");
|
2023-10-06 20:02:32 +01:00
|
|
|
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-08-02 15:30:04 +01:00
|
|
|
static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx,
|
|
|
|
const std::string metamethod
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
// Algebra Reduction Rules for comparison type functions
|
2024-02-23 20:08:34 +00:00
|
|
|
// 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-07-19 19:20:47 +01:00
|
|
|
// Implement injective type functions for comparison type functions
|
2024-03-01 18:45:26 +00:00
|
|
|
// 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-07-19 19:20:47 +01:00
|
|
|
if (auto result = tryDistributeTypeFunctionApp(comparisonTypeFunction, instance, typeParams, packParams, ctx, metamethod))
|
2024-05-31 20:18:18 +01:00
|
|
|
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
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
2024-08-23 17:35:30 +01:00
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
|
2023-10-13 21:20:12 +01:00
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->builtins->booleanType, false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> ltTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("lt type function: encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__lt");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> leTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("le type function: encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__le");
|
2023-10-13 21:20:12 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-10-13 21:20:12 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("eq type function: encountered a type function instance without the required argument structure");
|
2023-10-13 21:20:12 +01:00
|
|
|
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, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return {std::nullopt, true, {}, {}}; // if it's not, then this type function 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
|
|
|
|
|
2024-09-27 19:58:21 +01:00
|
|
|
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
2024-08-23 17:35:30 +01:00
|
|
|
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance?
|
2023-10-13 21:20:12 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2023-12-15 21:29:06 +00:00
|
|
|
{
|
2024-08-16 19:29:33 +01:00
|
|
|
if (typeParams.size() < 2 || !packParams.empty())
|
2023-12-15 21:29:06 +00:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("refine type function: encountered a type function instance without the required argument structure");
|
2023-12-15 21:29:06 +00:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TypeId targetTy = follow(typeParams.at(0));
|
2024-08-16 19:29:33 +01:00
|
|
|
std::vector<TypeId> discriminantTypes;
|
|
|
|
for (size_t i = 1; i < typeParams.size(); i++)
|
|
|
|
discriminantTypes.push_back(follow(typeParams.at(i)));
|
2023-12-15 21:29:06 +00:00
|
|
|
|
|
|
|
// 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}, {}};
|
2024-08-16 19:29:33 +01:00
|
|
|
else
|
2024-05-10 19:21:45 +01:00
|
|
|
{
|
2024-08-16 19:29:33 +01:00
|
|
|
for (auto t : discriminantTypes)
|
|
|
|
{
|
|
|
|
if (isPending(t, ctx->solver))
|
|
|
|
return {std::nullopt, false, {t}, {}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Refine a target type and a discriminant one at a time.
|
|
|
|
// Returns result : TypeId, toBlockOn : vector<TypeId>
|
|
|
|
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
|
|
|
{
|
|
|
|
std::vector<TypeId> toBlock;
|
|
|
|
if (ctx->solver)
|
|
|
|
{
|
|
|
|
std::optional<TypeId> targetMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, target);
|
|
|
|
std::optional<TypeId> discriminantMaybeGeneralized = ctx->solver->generalizeFreeType(ctx->scope, discriminant);
|
2024-05-10 19:21:45 +01:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
if (!targetMaybeGeneralized)
|
|
|
|
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {target}};
|
|
|
|
else if (!discriminantMaybeGeneralized)
|
|
|
|
return std::pair<TypeId, std::vector<TypeId>>{nullptr, {discriminant}};
|
2024-05-10 19:21:45 +01:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
target = *targetMaybeGeneralized;
|
|
|
|
discriminant = *discriminantMaybeGeneralized;
|
|
|
|
}
|
2024-05-10 19:21:45 +01:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
// we need a more complex check for blocking on the discriminant in particular
|
|
|
|
FindRefinementBlockers frb;
|
|
|
|
frb.traverse(discriminant);
|
|
|
|
|
|
|
|
if (!frb.found.empty())
|
|
|
|
return {nullptr, {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>(discriminant))
|
|
|
|
if (get<AnyType>(follow(nt->ty)))
|
|
|
|
return {target, {}};
|
|
|
|
|
|
|
|
// If the target type is a table, then simplification already implements the logic to deal with refinements properly since the
|
|
|
|
// type of the discriminant is guaranteed to only ever be an (arbitrarily-nested) table of a single property type.
|
|
|
|
if (get<TableType>(target))
|
|
|
|
{
|
|
|
|
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
|
|
|
|
if (!result.blockedTypes.empty())
|
|
|
|
return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}};
|
2024-06-14 21:21:20 +01:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
return {result.result, {}};
|
|
|
|
}
|
2023-12-15 21:29:06 +00:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
// In the general case, we'll still use normalization though.
|
|
|
|
TypeId intersection = ctx->arena->addType(IntersectionType{{target, discriminant}});
|
|
|
|
std::shared_ptr<const NormalizedType> normIntersection = ctx->normalizer->normalize(intersection);
|
|
|
|
std::shared_ptr<const NormalizedType> normType = ctx->normalizer->normalize(target);
|
|
|
|
|
|
|
|
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!normIntersection || !normType)
|
|
|
|
return {nullptr, {}};
|
2023-12-15 21:29:06 +00:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
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}});
|
2023-12-15 21:29:06 +00:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
return {resultTy, {}};
|
|
|
|
};
|
|
|
|
|
|
|
|
// refine target with each discriminant type in sequence (reverse of insertion order)
|
|
|
|
// If we cannot proceed, block. If all discriminant types refine successfully, return
|
|
|
|
// the result
|
|
|
|
TypeId target = targetTy;
|
|
|
|
while (!discriminantTypes.empty())
|
|
|
|
{
|
|
|
|
TypeId discriminant = discriminantTypes.back();
|
|
|
|
auto [refined, blocked] = stepRefine(target, discriminant);
|
2023-12-15 21:29:06 +00:00
|
|
|
|
2024-08-16 19:29:33 +01:00
|
|
|
if (blocked.empty() && refined == nullptr)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
if (!blocked.empty())
|
|
|
|
return {std::nullopt, false, blocked, {}};
|
|
|
|
|
|
|
|
target = refined;
|
|
|
|
discriminantTypes.pop_back();
|
|
|
|
}
|
|
|
|
return {target, false, {}, {}};
|
2023-12-15 21:29:06 +00:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> singletonTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> 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-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("singleton type function: encountered a type function 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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> unionTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-04-19 22:48:02 +01:00
|
|
|
{
|
|
|
|
if (!packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("union type function: encountered a type function instance without the required argument structure");
|
2024-04-19 22:48:02 +01:00
|
|
|
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-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> 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
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("intersect type function: encountered a type function 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
|
|
|
// 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-07-12 18:03:36 +01:00
|
|
|
bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> 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;
|
|
|
|
}
|
|
|
|
|
2024-09-06 21:34:50 +01:00
|
|
|
if (auto classTy = get<ClassType>(ty))
|
|
|
|
{
|
|
|
|
for (auto [key, _] : classTy->props)
|
|
|
|
result.insert(key);
|
|
|
|
|
|
|
|
bool res = true;
|
|
|
|
if (classTy->metatable && !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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (classTy->parent)
|
|
|
|
res = res && computeKeysOf(follow(*classTy->parent), result, seen, isRaw, ctx);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this should not be reachable since the type should be a valid tables or classes part from normalization.
|
2024-01-12 22:25:27 +00:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
|
2024-08-02 15:30:04 +01:00
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx,
|
|
|
|
bool isRaw
|
|
|
|
)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
|
2024-01-12 22:25:27 +00:00
|
|
|
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());
|
|
|
|
|
2024-09-06 21:34:50 +01:00
|
|
|
// seen set for key computation for classes
|
|
|
|
DenseHashSet<TypeId> seen{{}};
|
|
|
|
|
2024-01-12 22:25:27 +00:00
|
|
|
auto classesIter = normTy->classes.ordering.begin();
|
|
|
|
auto classesIterEnd = normTy->classes.ordering.end();
|
2024-09-06 21:34:50 +01:00
|
|
|
LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier
|
2024-01-12 22:25:27 +00:00
|
|
|
|
2024-09-06 21:34:50 +01:00
|
|
|
// collect all the properties from the first class type
|
|
|
|
if (!computeKeysOf(*classesIter, keys, seen, isRaw, ctx))
|
|
|
|
return {ctx->builtins->stringType, false, {}, {}}; // if it failed, we have a top type!
|
2024-01-12 22:25:27 +00:00
|
|
|
|
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)
|
|
|
|
{
|
2024-09-06 21:34:50 +01:00
|
|
|
seen.clear(); // we'll reuse the same seen set
|
|
|
|
|
|
|
|
Set<std::string> localKeys{{}};
|
|
|
|
|
|
|
|
// we can skip to the next class if this one is a top type
|
|
|
|
if (!computeKeysOf(*classesIter, localKeys, seen, isRaw, ctx))
|
|
|
|
continue;
|
2024-01-12 22:25:27 +00:00
|
|
|
|
2024-09-27 19:58:21 +01: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
|
2024-09-06 21:34:50 +01:00
|
|
|
if (!localKeys.contains(key))
|
2024-01-19 18:04:46 +00:00
|
|
|
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-09-27 19:58:21 +01:00
|
|
|
for (auto& key : keys)
|
2024-01-19 18:04:46 +00:00
|
|
|
{
|
|
|
|
// 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}}));
|
|
|
|
|
2024-09-09 21:51:33 +01:00
|
|
|
// If there's only one entry, we don't need a UnionType.
|
|
|
|
// We can take straight take it from the first entry
|
|
|
|
// because it was added into the type arena already.
|
|
|
|
if (singletons.size() == 1)
|
|
|
|
return {singletons.front(), false, {}, {}};
|
|
|
|
|
2024-01-12 22:25:27 +00:00
|
|
|
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> keyofTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("keyof type function: encountered a type function instance without the required argument structure");
|
2024-01-12 22:25:27 +00:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
2024-01-12 22:25:27 +00:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> rawkeyofTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-01-12 22:25:27 +00:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 1 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("rawkeyof type function: encountered a type function instance without the required argument structure");
|
2024-01-12 22:25:27 +00:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
2024-01-12 22:25:27 +00:00
|
|
|
}
|
|
|
|
|
2024-06-14 21:21:20 +01:00
|
|
|
/* Searches through table's or class's props/indexer to find the property of `ty`
|
|
|
|
If found, appends that property to `result` and returns true
|
|
|
|
Else, returns false */
|
|
|
|
bool searchPropsAndIndexer(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId ty,
|
|
|
|
TableType::Props tblProps,
|
|
|
|
std::optional<TableIndexer> tblIndexer,
|
|
|
|
DenseHashSet<TypeId>& result,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-06-14 21:21:20 +01:00
|
|
|
{
|
|
|
|
ty = follow(ty);
|
|
|
|
|
|
|
|
// index into tbl's properties
|
|
|
|
if (auto stringSingleton = get<StringSingleton>(get<SingletonType>(ty)))
|
|
|
|
{
|
|
|
|
if (tblProps.find(stringSingleton->value) != tblProps.end())
|
|
|
|
{
|
|
|
|
TypeId propTy = follow(tblProps.at(stringSingleton->value).type());
|
|
|
|
|
|
|
|
// property is a union type -> we need to extend our reduction type
|
|
|
|
if (auto propUnionTy = get<UnionType>(propTy))
|
|
|
|
{
|
|
|
|
for (TypeId option : propUnionTy->options)
|
|
|
|
result.insert(option);
|
|
|
|
}
|
|
|
|
else // property is a singular type or intersection type -> we can simply append
|
|
|
|
result.insert(propTy);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// index into tbl's indexer
|
|
|
|
if (tblIndexer)
|
|
|
|
{
|
|
|
|
if (isSubtype(ty, tblIndexer->indexType, ctx->scope, ctx->builtins, *ctx->ice))
|
|
|
|
{
|
|
|
|
TypeId idxResultTy = follow(tblIndexer->indexResultType);
|
|
|
|
|
|
|
|
// indexResultType is a union type -> we need to extend our reduction type
|
|
|
|
if (auto idxResUnionTy = get<UnionType>(idxResultTy))
|
|
|
|
{
|
|
|
|
for (TypeId option : idxResUnionTy->options)
|
|
|
|
result.insert(option);
|
|
|
|
}
|
|
|
|
else // indexResultType is a singular type or intersection type -> we can simply append
|
|
|
|
result.insert(idxResultTy);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handles recursion / metamethods of tables/classes
|
|
|
|
`isRaw` parameter indicates whether or not we should follow __index metamethods
|
|
|
|
returns false if property of `ty` could not be found */
|
2024-07-12 18:03:36 +01:00
|
|
|
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
|
2024-06-14 21:21:20 +01:00
|
|
|
{
|
|
|
|
indexer = follow(indexer);
|
|
|
|
indexee = follow(indexee);
|
|
|
|
|
|
|
|
// we have a table type to try indexing
|
|
|
|
if (auto tableTy = get<TableType>(indexee))
|
|
|
|
{
|
|
|
|
return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a metatable type to try indexing
|
|
|
|
if (auto metatableTy = get<MetatableType>(indexee))
|
|
|
|
{
|
|
|
|
if (auto tableTy = get<TableType>(metatableTy->table))
|
|
|
|
{
|
|
|
|
|
|
|
|
// try finding all properties within the current scope of the table
|
|
|
|
if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the code reached here, it means we weren't able to find all properties -> look into __index metamethod
|
|
|
|
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, indexee, "__index", Location{});
|
|
|
|
if (mmType)
|
|
|
|
return tblIndexInto(indexer, *mmType, result, ctx, isRaw);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Vocabulary note: indexee refers to the type that contains the properties,
|
|
|
|
indexer refers to the type that is used to access indexee
|
|
|
|
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> indexFunctionImpl(
|
2024-08-02 15:30:04 +01:00
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx,
|
|
|
|
bool isRaw
|
|
|
|
)
|
2024-06-14 21:21:20 +01:00
|
|
|
{
|
|
|
|
TypeId indexeeTy = follow(typeParams.at(0));
|
|
|
|
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
|
|
|
|
|
|
|
|
// if the indexee failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!indexeeNormTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// if we don't have either just tables or just classes, we've got nothing to index into
|
|
|
|
if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// we're trying to reject any type that has not normalized to a table/class or a union of tables/classes.
|
|
|
|
if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() ||
|
|
|
|
indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() ||
|
|
|
|
indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
TypeId indexerTy = follow(typeParams.at(1));
|
2024-09-13 18:16:30 +01:00
|
|
|
|
|
|
|
if (isPending(indexerTy, ctx->solver))
|
|
|
|
return {std::nullopt, false, {indexerTy}, {}};
|
|
|
|
|
2024-06-14 21:21:20 +01:00
|
|
|
std::shared_ptr<const NormalizedType> indexerNormTy = ctx->normalizer->normalize(indexerTy);
|
|
|
|
|
|
|
|
// if the indexer failed to normalize, we can't reduce, but know nothing about inhabitance.
|
|
|
|
if (!indexerNormTy)
|
|
|
|
return {std::nullopt, false, {}, {}};
|
|
|
|
|
|
|
|
// we're trying to reject any type that is not a string singleton or primitive (string, number, boolean, thread, nil, function, table, or buffer)
|
|
|
|
if (indexerNormTy->hasTops() || indexerNormTy->hasErrors())
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
// indexer can be a union —> break them down into a vector
|
2024-09-27 19:58:21 +01:00
|
|
|
const std::vector<TypeId>* typesToFind = nullptr;
|
2024-06-14 21:21:20 +01:00
|
|
|
const std::vector<TypeId> singleType{indexerTy};
|
|
|
|
if (auto unionTy = get<UnionType>(indexerTy))
|
|
|
|
typesToFind = &unionTy->options;
|
|
|
|
else
|
|
|
|
typesToFind = &singleType;
|
|
|
|
|
|
|
|
DenseHashSet<TypeId> properties{{}}; // vector of types that will be returned
|
|
|
|
|
|
|
|
if (indexeeNormTy->hasClasses())
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!indexeeNormTy->hasTables());
|
|
|
|
|
2024-06-29 01:34:49 +01:00
|
|
|
if (isRaw) // rawget should never reduce for classes (to match the behavior of the rawget global function)
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
2024-06-14 21:21:20 +01:00
|
|
|
// at least one class is guaranteed to be in the iterator by .hasClasses()
|
|
|
|
for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter)
|
|
|
|
{
|
|
|
|
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 (TypeId ty : *typesToFind)
|
|
|
|
{
|
2024-06-29 01:34:49 +01:00
|
|
|
// Search for all instances of indexer in class->props and class->indexer
|
2024-06-14 21:21:20 +01:00
|
|
|
if (searchPropsAndIndexer(ty, classTy->props, classTy->indexer, properties, ctx))
|
|
|
|
continue; // Indexer was found in this class, so we can move on to the next
|
|
|
|
|
2024-09-06 21:34:50 +01:00
|
|
|
auto parent = classTy->parent;
|
|
|
|
bool foundInParent = false;
|
|
|
|
while (parent && !foundInParent)
|
|
|
|
{
|
|
|
|
auto parentClass = get<ClassType>(follow(*parent));
|
|
|
|
foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx);
|
|
|
|
parent = parentClass->parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we move on to the next type if any of the parents we went through had the property.
|
|
|
|
if (foundInParent)
|
|
|
|
continue;
|
|
|
|
|
2024-06-14 21:21:20 +01:00
|
|
|
// If code reaches here,that means the property not found -> check in the metatable's __index
|
|
|
|
|
|
|
|
// 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, *classesIter, "__index", Location{});
|
|
|
|
if (!mmType) // if a metatable does not exist, there is no where else to look
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
|
|
|
|
if (!tblIndexInto(ty, *mmType, properties, ctx, isRaw)) // if indexer is not in the metatable, we fail to reduce
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (indexeeNormTy->hasTables())
|
|
|
|
{
|
|
|
|
LUAU_ASSERT(!indexeeNormTy->hasClasses());
|
|
|
|
|
|
|
|
// at least one table is guaranteed to be in the iterator by .hasTables()
|
|
|
|
for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter)
|
|
|
|
{
|
|
|
|
for (TypeId ty : *typesToFind)
|
|
|
|
if (!tblIndexInto(ty, *tablesIter, properties, ctx, isRaw))
|
|
|
|
return {std::nullopt, true, {}, {}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call `follow()` on each element to resolve all Bound types before returning
|
2024-08-02 15:30:04 +01:00
|
|
|
std::transform(
|
|
|
|
properties.begin(),
|
|
|
|
properties.end(),
|
|
|
|
properties.begin(),
|
|
|
|
[](TypeId ty)
|
|
|
|
{
|
|
|
|
return follow(ty);
|
|
|
|
}
|
|
|
|
);
|
2024-06-14 21:21:20 +01:00
|
|
|
|
|
|
|
// If the type being reduced to is a single type, no need to union
|
|
|
|
if (properties.size() == 1)
|
|
|
|
return {*properties.begin(), false, {}, {}};
|
|
|
|
|
|
|
|
return {ctx->arena->addType(UnionType{std::vector<TypeId>(properties.begin(), properties.end())}), false, {}, {}};
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> indexTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-06-29 01:34:49 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("index type function: encountered a type function instance without the required argument structure");
|
2024-06-29 01:34:49 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
2024-06-29 01:34:49 +01:00
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
|
2024-08-02 15:30:04 +01:00
|
|
|
TypeId instance,
|
|
|
|
const std::vector<TypeId>& typeParams,
|
|
|
|
const std::vector<TypePackId>& packParams,
|
|
|
|
NotNull<TypeFunctionContext> ctx
|
|
|
|
)
|
2024-06-29 01:34:49 +01:00
|
|
|
{
|
|
|
|
if (typeParams.size() != 2 || !packParams.empty())
|
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
ctx->ice->ice("rawget type function: encountered a type function instance without the required argument structure");
|
2024-06-29 01:34:49 +01:00
|
|
|
LUAU_ASSERT(false);
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:20:47 +01:00
|
|
|
return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
|
2024-06-29 01:34:49 +01:00
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
BuiltinTypeFunctions::BuiltinTypeFunctions()
|
2024-08-09 18:18:20 +01:00
|
|
|
: userFunc{"user", userDefinedTypeFunction}
|
|
|
|
, notFunc{"not", notTypeFunction}
|
2024-07-19 19:20:47 +01:00
|
|
|
, lenFunc{"len", lenTypeFunction}
|
|
|
|
, unmFunc{"unm", unmTypeFunction}
|
|
|
|
, addFunc{"add", addTypeFunction}
|
|
|
|
, subFunc{"sub", subTypeFunction}
|
|
|
|
, mulFunc{"mul", mulTypeFunction}
|
|
|
|
, divFunc{"div", divTypeFunction}
|
|
|
|
, idivFunc{"idiv", idivTypeFunction}
|
|
|
|
, powFunc{"pow", powTypeFunction}
|
|
|
|
, modFunc{"mod", modTypeFunction}
|
|
|
|
, concatFunc{"concat", concatTypeFunction}
|
|
|
|
, andFunc{"and", andTypeFunction}
|
|
|
|
, orFunc{"or", orTypeFunction}
|
|
|
|
, ltFunc{"lt", ltTypeFunction}
|
|
|
|
, leFunc{"le", leTypeFunction}
|
|
|
|
, eqFunc{"eq", eqTypeFunction}
|
|
|
|
, refineFunc{"refine", refineTypeFunction}
|
|
|
|
, singletonFunc{"singleton", singletonTypeFunction}
|
|
|
|
, unionFunc{"union", unionTypeFunction}
|
|
|
|
, intersectFunc{"intersect", intersectTypeFunction}
|
|
|
|
, keyofFunc{"keyof", keyofTypeFunction}
|
|
|
|
, rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
|
|
|
|
, indexFunc{"index", indexTypeFunction}
|
|
|
|
, rawgetFunc{"rawget", rawgetTypeFunction}
|
2023-10-06 20:02:32 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const
|
2023-05-19 20:37:30 +01:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
// make a type function for a one-argument type function
|
2024-08-02 15:30:04 +01:00
|
|
|
auto mkUnaryTypeFunction = [&](const TypeFunction* tf)
|
|
|
|
{
|
2023-10-21 02:10:30 +01:00
|
|
|
TypeId t = arena->addType(GenericType{"T"});
|
|
|
|
GenericTypeDefinition genericT{t};
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
return TypeFun{{genericT}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t}, {}})};
|
2023-10-21 02:10:30 +01:00
|
|
|
};
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
// make a type function for a two-argument type function
|
2024-08-02 15:30:04 +01:00
|
|
|
auto mkBinaryTypeFunction = [&](const TypeFunction* tf)
|
|
|
|
{
|
2023-10-06 20:02:32 +01:00
|
|
|
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
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
return TypeFun{{genericT, genericU}, arena->addType(TypeFunctionInstanceType{NotNull{tf}, {t, u}, {}})};
|
2023-10-06 20:02:32 +01:00
|
|
|
};
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
scope->exportedTypeBindings[lenFunc.name] = mkUnaryTypeFunction(&lenFunc);
|
|
|
|
scope->exportedTypeBindings[unmFunc.name] = mkUnaryTypeFunction(&unmFunc);
|
2023-10-21 02:10:30 +01:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
scope->exportedTypeBindings[addFunc.name] = mkBinaryTypeFunction(&addFunc);
|
|
|
|
scope->exportedTypeBindings[subFunc.name] = mkBinaryTypeFunction(&subFunc);
|
|
|
|
scope->exportedTypeBindings[mulFunc.name] = mkBinaryTypeFunction(&mulFunc);
|
|
|
|
scope->exportedTypeBindings[divFunc.name] = mkBinaryTypeFunction(&divFunc);
|
|
|
|
scope->exportedTypeBindings[idivFunc.name] = mkBinaryTypeFunction(&idivFunc);
|
|
|
|
scope->exportedTypeBindings[powFunc.name] = mkBinaryTypeFunction(&powFunc);
|
|
|
|
scope->exportedTypeBindings[modFunc.name] = mkBinaryTypeFunction(&modFunc);
|
|
|
|
scope->exportedTypeBindings[concatFunc.name] = mkBinaryTypeFunction(&concatFunc);
|
2023-10-13 21:20:12 +01:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunction(<Func);
|
|
|
|
scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunction(&leFunc);
|
|
|
|
scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunction(&eqFunc);
|
2024-01-12 22:25:27 +00:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc);
|
|
|
|
scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc);
|
2024-06-14 21:21:20 +01:00
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
scope->exportedTypeBindings[indexFunc.name] = mkBinaryTypeFunction(&indexFunc);
|
|
|
|
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunction(&rawgetFunc);
|
2024-06-29 01:34:49 +01:00
|
|
|
}
|
|
|
|
|
2024-07-12 18:03:36 +01:00
|
|
|
const BuiltinTypeFunctions& builtinTypeFunctions()
|
2024-06-29 01:34:49 +01:00
|
|
|
{
|
2024-07-12 18:03:36 +01:00
|
|
|
static std::unique_ptr<const BuiltinTypeFunctions> result = std::make_unique<BuiltinTypeFunctions>();
|
2024-06-29 01:34:49 +01:00
|
|
|
|
|
|
|
return *result;
|
2023-05-12 18:50:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Luau
|