2023-05-12 18:50:47 +01:00
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
# include "Luau/TypeFamily.h"
2024-02-16 02:04:39 +00:00
# include "Luau/Common.h"
2023-09-22 20:12:15 +01:00
# include "Luau/ConstraintSolver.h"
2023-05-12 18:50:47 +01:00
# include "Luau/DenseHash.h"
2023-08-04 20:18:54 +01:00
# include "Luau/Instantiation.h"
# include "Luau/Normalize.h"
2024-02-23 20:08:34 +00:00
# include "Luau/NotNull.h"
2024-03-09 00:47:53 +00:00
# include "Luau/OverloadResolution.h"
# include "Luau/Set.h"
2023-10-06 20:02:32 +01:00
# include "Luau/Simplify.h"
2023-05-12 18:50:47 +01:00
# include "Luau/Substitution.h"
2023-10-13 21:20:12 +01:00
# include "Luau/Subtyping.h"
2023-05-12 18:50:47 +01:00
# include "Luau/ToString.h"
2023-08-04 20:18:54 +01:00
# include "Luau/TxnLog.h"
2024-02-16 02:04:39 +00:00
# include "Luau/Type.h"
2023-08-04 20:18:54 +01:00
# include "Luau/TypeCheckLimits.h"
2024-03-30 23:14:44 +00:00
# include "Luau/TypeFamilyReductionGuesser.h"
2024-03-01 18:45:26 +00:00
# include "Luau/TypeFwd.h"
2023-05-19 20:37:30 +01:00
# include "Luau/TypeUtils.h"
2023-10-06 20:02:32 +01:00
# include "Luau/Unifier2.h"
2024-01-12 22:25:27 +00:00
# include "Luau/VecDeque.h"
2023-08-04 20:18:54 +01:00
# include "Luau/VisitType.h"
2023-05-12 18:50:47 +01:00
2024-03-30 23:14:44 +00:00
// used to control emitting CodeTooComplex warnings on type family reduction
2023-05-12 18:50:47 +01:00
LUAU_DYNAMIC_FASTINTVARIABLE ( LuauTypeFamilyGraphReductionMaximumSteps , 1'000'000 ) ;
2024-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-03-22 17:47:10 +00:00
LUAU_FASTFLAG ( DebugLuauLogSolver ) ;
2023-05-12 18:50:47 +01:00
namespace Luau
{
2024-03-30 23:14:44 +00:00
using TypeOrTypePackIdSet = DenseHashSet < const void * > ;
2023-05-12 18:50:47 +01:00
struct InstanceCollector : TypeOnceVisitor
{
2024-01-12 22:25:27 +00:00
VecDeque < TypeId > tys ;
VecDeque < TypePackId > tps ;
2024-03-30 23:14:44 +00:00
TypeOrTypePackIdSet shouldGuess { nullptr } ;
2024-02-16 02:04:39 +00:00
std : : vector < TypeId > cyclicInstance ;
2023-05-12 18:50:47 +01:00
bool visit ( TypeId ty , const TypeFamilyInstanceType & ) override
{
// TypeOnceVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
2024-03-30 23:14:44 +00:00
if ( DFInt : : LuauTypeFamilyUseGuesserDepth > = 0 & & typeFamilyDepth > DFInt : : LuauTypeFamilyUseGuesserDepth )
shouldGuess . insert ( ty ) ;
2023-05-12 18:50:47 +01:00
tys . push_front ( ty ) ;
2024-03-30 23:14:44 +00:00
2023-05-12 18:50:47 +01:00
return true ;
}
2024-02-16 02:04:39 +00:00
void cycle ( TypeId ty ) override
{
/// Detected cyclic type pack
TypeId t = follow ( ty ) ;
if ( get < TypeFamilyInstanceType > ( t ) )
cyclicInstance . push_back ( t ) ;
}
2023-05-19 20:37:30 +01:00
bool visit ( TypeId ty , const ClassType & ) override
{
return false ;
}
2023-05-12 18:50:47 +01:00
bool visit ( TypePackId tp , const TypeFamilyInstanceTypePack & ) override
{
// TypeOnceVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
2024-03-30 23:14:44 +00:00
if ( DFInt : : LuauTypeFamilyUseGuesserDepth > = 0 & & typeFamilyDepth > DFInt : : LuauTypeFamilyUseGuesserDepth )
shouldGuess . insert ( tp ) ;
2023-05-12 18:50:47 +01:00
tps . push_front ( tp ) ;
2024-03-30 23:14:44 +00:00
2023-05-12 18:50:47 +01:00
return true ;
}
} ;
struct FamilyReducer
{
2023-10-06 20:02:32 +01:00
TypeFamilyContext ctx ;
2023-09-22 20:12:15 +01:00
2024-01-12 22:25:27 +00:00
VecDeque < TypeId > queuedTys ;
VecDeque < TypePackId > queuedTps ;
2024-03-30 23:14:44 +00:00
TypeOrTypePackIdSet shouldGuess ;
2024-02-16 02:04:39 +00:00
std : : vector < TypeId > cyclicTypeFamilies ;
2024-03-30 23:14:44 +00:00
TypeOrTypePackIdSet irreducible { nullptr } ;
2023-05-12 18:50:47 +01:00
FamilyGraphReductionResult result ;
bool force = false ;
2023-09-22 20:12:15 +01:00
// Local to the constraint being reduced.
Location location ;
2023-10-06 20:02:32 +01:00
2024-03-30 23:14:44 +00:00
FamilyReducer ( VecDeque < TypeId > queuedTys , VecDeque < TypePackId > queuedTps , TypeOrTypePackIdSet shouldGuess , std : : vector < TypeId > cyclicTypes ,
Location location , TypeFamilyContext ctx , bool force = false )
2023-10-06 20:02:32 +01:00
: ctx ( ctx )
2023-09-22 20:12:15 +01:00
, queuedTys ( std : : move ( queuedTys ) )
, queuedTps ( std : : move ( queuedTps ) )
2024-03-30 23:14:44 +00:00
, shouldGuess ( std : : move ( shouldGuess ) )
2024-02-16 02:04:39 +00:00
, cyclicTypeFamilies ( std : : move ( cyclicTypes ) )
2023-05-12 18:50:47 +01:00
, force ( force )
2023-09-22 20:12:15 +01:00
, location ( location )
2023-05-12 18:50:47 +01:00
{
}
enum class SkipTestResult
{
2024-02-16 02:04:39 +00:00
CyclicTypeFamily ,
2023-05-12 18:50:47 +01:00
Irreducible ,
Defer ,
Okay ,
} ;
SkipTestResult testForSkippability ( TypeId ty )
{
2023-10-06 20:02:32 +01:00
ty = follow ( ty ) ;
2023-05-12 18:50:47 +01:00
2023-10-06 20:02:32 +01:00
if ( is < TypeFamilyInstanceType > ( ty ) )
2023-05-12 18:50:47 +01:00
{
2024-02-16 02:04:39 +00:00
for ( auto t : cyclicTypeFamilies )
{
if ( ty = = t )
return SkipTestResult : : CyclicTypeFamily ;
}
2023-05-12 18:50:47 +01:00
if ( ! irreducible . contains ( ty ) )
return SkipTestResult : : Defer ;
2024-02-16 02:04:39 +00:00
return SkipTestResult : : Irreducible ;
2023-05-12 18:50:47 +01:00
}
2023-10-06 20:02:32 +01:00
else if ( is < GenericType > ( ty ) )
2023-05-12 18:50:47 +01:00
{
return SkipTestResult : : Irreducible ;
}
return SkipTestResult : : Okay ;
}
SkipTestResult testForSkippability ( TypePackId ty )
{
2023-10-06 20:02:32 +01:00
ty = follow ( ty ) ;
2023-05-12 18:50:47 +01:00
2023-10-06 20:02:32 +01:00
if ( is < TypeFamilyInstanceTypePack > ( ty ) )
2023-05-12 18:50:47 +01:00
{
if ( ! irreducible . contains ( ty ) )
return SkipTestResult : : Defer ;
else
return SkipTestResult : : Irreducible ;
}
2023-10-06 20:02:32 +01:00
else if ( is < GenericTypePack > ( ty ) )
2023-05-12 18:50:47 +01:00
{
return SkipTestResult : : Irreducible ;
}
return SkipTestResult : : Okay ;
}
template < typename T >
void replace ( T subject , T replacement )
{
2024-03-22 17:47:10 +00:00
if ( subject - > owningArena ! = ctx . arena . get ( ) )
ctx . ice - > ice ( " Attempting to modify a type family instance from another arena " , location ) ;
2024-03-30 23:14:44 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " %s -> %s \n " , toString ( subject , { true } ) . c_str ( ) , toString ( replacement , { true } ) . c_str ( ) ) ;
2023-10-06 20:02:32 +01:00
asMutable ( subject ) - > ty . template emplace < Unifiable : : Bound < T > > ( replacement ) ;
2023-05-12 18:50:47 +01:00
if constexpr ( std : : is_same_v < T , TypeId > )
result . reducedTypes . insert ( subject ) ;
else if constexpr ( std : : is_same_v < T , TypePackId > )
result . reducedPacks . insert ( subject ) ;
}
template < typename T >
void handleFamilyReduction ( T subject , TypeFamilyReductionResult < T > reduction )
{
if ( reduction . result )
replace ( subject , * reduction . result ) ;
else
{
irreducible . insert ( subject ) ;
if ( reduction . uninhabited | | force )
{
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " %s is uninhabited \n " , toString ( subject , { true } ) . c_str ( ) ) ;
2023-05-12 18:50:47 +01:00
if constexpr ( std : : is_same_v < T , TypeId > )
result . errors . push_back ( TypeError { location , UninhabitedTypeFamily { subject } } ) ;
else if constexpr ( std : : is_same_v < T , TypePackId > )
result . errors . push_back ( TypeError { location , UninhabitedTypePackFamily { subject } } ) ;
}
else if ( ! reduction . uninhabited & & ! force )
{
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " %s is irreducible; blocked on %zu types, %zu packs \n " , toString ( subject , { true } ) . c_str ( ) , reduction . blockedTypes . size ( ) , reduction . blockedPacks . size ( ) ) ;
2023-05-12 18:50:47 +01:00
for ( TypeId b : reduction . blockedTypes )
result . blockedTypes . insert ( b ) ;
for ( TypePackId b : reduction . blockedPacks )
result . blockedPacks . insert ( b ) ;
}
}
}
bool done ( )
{
return queuedTys . empty ( ) & & queuedTps . empty ( ) ;
}
template < typename T , typename I >
bool testParameters ( T subject , const I * tfit )
{
for ( TypeId p : tfit - > typeArguments )
{
SkipTestResult skip = testForSkippability ( p ) ;
if ( skip = = SkipTestResult : : Irreducible )
{
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " %s is irreducible due to a dependency on %s \n " , toString ( subject , { true } ) . c_str ( ) , toString ( p , { true } ) . c_str ( ) ) ;
2023-05-12 18:50:47 +01:00
irreducible . insert ( subject ) ;
return false ;
}
else if ( skip = = SkipTestResult : : Defer )
{
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " Deferring %s until %s is solved \n " , toString ( subject , { true } ) . c_str ( ) , toString ( p , { true } ) . c_str ( ) ) ;
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-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " %s is irreducible due to a dependency on %s \n " , toString ( subject , { true } ) . c_str ( ) , toString ( p , { true } ) . c_str ( ) ) ;
2023-05-12 18:50:47 +01:00
irreducible . insert ( subject ) ;
return false ;
}
else if ( skip = = SkipTestResult : : Defer )
{
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " Deferring %s until %s is solved \n " , toString ( subject , { true } ) . c_str ( ) , toString ( p , { true } ) . c_str ( ) ) ;
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 ) )
{
if ( FFlag : : DebugLuauLogSolver )
printf ( " Flagged %s for reduction with guesser. \n " , toString ( subject , { true } ) . c_str ( ) ) ;
TypeFamilyReductionGuesser guesser { ctx . arena , ctx . builtins , ctx . normalizer } ;
auto guessed = guesser . guess ( subject ) ;
if ( guessed )
{
if ( FFlag : : DebugLuauLogSolver )
printf ( " Selected %s as the guessed result type. \n " , toString ( * guessed , { true } ) . c_str ( ) ) ;
replace ( subject , * guessed ) ;
return true ;
}
if ( FFlag : : DebugLuauLogSolver )
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-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " Trying to reduce %s \n " , toString ( subject , { true } ) . c_str ( ) ) ;
2023-10-06 20:02:32 +01:00
if ( const TypeFamilyInstanceType * tfit = get < TypeFamilyInstanceType > ( subject ) )
2023-05-12 18:50:47 +01:00
{
2024-02-16 02:04:39 +00:00
SkipTestResult testCyclic = testForSkippability ( subject ) ;
if ( ! testParameters ( subject , tfit ) & & testCyclic ! = SkipTestResult : : CyclicTypeFamily )
2024-03-22 17:47:10 +00:00
{
if ( FFlag : : DebugLuauLogSolver )
printf ( " Irreducible due to irreducible/pending and a non-cyclic family \n " ) ;
2023-05-12 18:50:47 +01:00
return ;
2024-03-22 17:47:10 +00:00
}
2023-05-12 18:50:47 +01:00
2024-03-30 23:14:44 +00:00
if ( tryGuessing ( subject ) )
return ;
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > result = tfit - > family - > reducer ( subject , tfit - > typeArguments , tfit - > packArguments , NotNull { & ctx } ) ;
2023-05-12 18:50:47 +01:00
handleFamilyReduction ( subject , result ) ;
}
}
void stepPack ( )
{
2023-10-06 20:02:32 +01:00
TypePackId subject = follow ( queuedTps . front ( ) ) ;
2023-05-12 18:50:47 +01:00
queuedTps . pop_front ( ) ;
if ( irreducible . contains ( subject ) )
return ;
2024-03-22 17:47:10 +00:00
if ( FFlag : : DebugLuauLogSolver )
printf ( " Trying to reduce %s \n " , toString ( subject , { true } ) . c_str ( ) ) ;
2023-10-06 20:02:32 +01:00
if ( const TypeFamilyInstanceTypePack * tfit = get < TypeFamilyInstanceTypePack > ( subject ) )
2023-05-12 18:50:47 +01:00
{
if ( ! testParameters ( subject , tfit ) )
return ;
2024-03-30 23:14:44 +00:00
if ( tryGuessing ( subject ) )
return ;
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypePackId > result = tfit - > family - > reducer ( subject , tfit - > typeArguments , tfit - > packArguments , NotNull { & ctx } ) ;
2023-05-12 18:50:47 +01:00
handleFamilyReduction ( subject , result ) ;
}
}
void step ( )
{
if ( ! queuedTys . empty ( ) )
stepType ( ) ;
else if ( ! queuedTps . empty ( ) )
stepPack ( ) ;
}
} ;
2023-10-06 20:02:32 +01:00
static FamilyGraphReductionResult reduceFamiliesInternal (
2024-03-30 23:14:44 +00:00
VecDeque < TypeId > queuedTys , VecDeque < TypePackId > queuedTps , TypeOrTypePackIdSet shouldGuess , std : : vector < TypeId > cyclics , Location location , TypeFamilyContext ctx , bool force )
2023-05-12 18:50:47 +01:00
{
2024-03-30 23:14:44 +00:00
FamilyReducer reducer { std : : move ( queuedTys ) , std : : move ( queuedTps ) , std : : move ( shouldGuess ) , std : : move ( cyclics ) , location , ctx , force } ;
2023-05-12 18:50:47 +01:00
int iterationCount = 0 ;
while ( ! reducer . done ( ) )
{
reducer . step ( ) ;
+ + iterationCount ;
if ( iterationCount > DFInt : : LuauTypeFamilyGraphReductionMaximumSteps )
{
reducer . result . errors . push_back ( TypeError { location , CodeTooComplex { } } ) ;
break ;
}
}
return std : : move ( reducer . result ) ;
}
2023-10-06 20:02:32 +01:00
FamilyGraphReductionResult reduceFamilies ( TypeId entrypoint , Location location , TypeFamilyContext ctx , bool force )
2023-09-22 20:12:15 +01:00
{
InstanceCollector collector ;
try
{
collector . traverse ( entrypoint ) ;
}
catch ( RecursionLimitException & )
{
return FamilyGraphReductionResult { } ;
}
if ( collector . tys . empty ( ) & & collector . tps . empty ( ) )
return { } ;
2024-03-30 23:14:44 +00:00
return reduceFamiliesInternal ( std : : move ( collector . tys ) , std : : move ( collector . tps ) , std : : move ( collector . shouldGuess ) , std : : move ( collector . cyclicInstance ) , location , ctx , force ) ;
2023-09-22 20:12:15 +01:00
}
2023-10-06 20:02:32 +01:00
FamilyGraphReductionResult reduceFamilies ( TypePackId entrypoint , Location location , TypeFamilyContext ctx , bool force )
2023-09-22 20:12:15 +01:00
{
InstanceCollector collector ;
try
{
collector . traverse ( entrypoint ) ;
}
catch ( RecursionLimitException & )
{
return FamilyGraphReductionResult { } ;
}
if ( collector . tys . empty ( ) & & collector . tps . empty ( ) )
return { } ;
2024-03-30 23:14:44 +00:00
return reduceFamiliesInternal ( std : : move ( collector . tys ) , std : : move ( collector . tps ) , std : : move ( collector . shouldGuess ) , std : : move ( collector . cyclicInstance ) , location , ctx , force ) ;
2023-05-19 20:37:30 +01:00
}
2023-10-06 20:02:32 +01:00
bool isPending ( TypeId ty , ConstraintSolver * solver )
2023-05-19 20:37:30 +01:00
{
2024-03-22 17:47:10 +00:00
return is < BlockedType , PendingExpansionType , TypeFamilyInstanceType , LocalType > ( ty ) | | ( solver & & solver - > hasUnresolvedConstraints ( ty ) ) ;
2023-05-19 20:37:30 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > notFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " not type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId ty = follow ( typeParams . at ( 0 ) ) ;
if ( isPending ( ty , ctx - > solver ) )
return { std : : nullopt , false , { ty } , { } } ;
// `not` operates on anything and returns a `boolean` always.
return { ctx - > builtins - > booleanType , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > lenFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-21 02:10:30 +01:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " len type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId operandTy = follow ( typeParams . at ( 0 ) ) ;
2023-10-27 22:18:41 +01:00
// check to see if the operand type is resolved enough, and wait to reduce if not
2023-12-15 21:29:06 +00:00
// the use of `typeFromNormal` later necessitates blocking on local types.
if ( isPending ( operandTy , ctx - > solver ) | | get < LocalType > ( operandTy ) )
2023-10-27 22:18:41 +01:00
return { std : : nullopt , false , { operandTy } , { } } ;
2023-10-21 02:10:30 +01:00
const NormalizedType * normTy = ctx - > normalizer - > normalize ( operandTy ) ;
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normTy )
return { std : : nullopt , false , { } , { } } ;
// if the operand type is error suppressing, we can immediately reduce to `number`.
if ( normTy - > shouldSuppressErrors ( ) )
return { ctx - > builtins - > numberType , false , { } , { } } ;
// if we have a `never`, we can never observe that the operator didn't work.
if ( is < NeverType > ( operandTy ) )
return { ctx - > builtins - > neverType , false , { } , { } } ;
// if we're checking the length of a string, that works!
if ( normTy - > isSubtypeOfString ( ) )
return { ctx - > builtins - > numberType , false , { } , { } } ;
// we use the normalized operand here in case there was an intersection or union.
TypeId normalizedOperand = ctx - > normalizer - > typeFromNormal ( * normTy ) ;
if ( normTy - > hasTopTable ( ) | | get < TableType > ( normalizedOperand ) )
return { ctx - > builtins - > numberType , false , { } , { } } ;
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , operandTy , " __len " , Location { } ) ;
if ( ! mmType )
return { std : : nullopt , true , { } , { } } ;
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
const FunctionType * mmFtv = get < FunctionType > ( * mmType ) ;
if ( ! mmFtv )
return { std : : nullopt , true , { } , { } } ;
std : : optional < TypeId > instantiatedMmType = instantiate ( ctx - > builtins , ctx - > arena , ctx - > limits , ctx - > scope , * mmType ) ;
if ( ! instantiatedMmType )
return { std : : nullopt , true , { } , { } } ;
const FunctionType * instantiatedMmFtv = get < FunctionType > ( * instantiatedMmType ) ;
if ( ! instantiatedMmFtv )
return { ctx - > builtins - > errorRecoveryType ( ) , false , { } , { } } ;
TypePackId inferredArgPack = ctx - > arena - > addTypePack ( { operandTy } ) ;
Unifier2 u2 { ctx - > arena , ctx - > builtins , ctx - > scope , ctx - > ice } ;
if ( ! u2 . unify ( inferredArgPack , instantiatedMmFtv - > argTypes ) )
return { std : : nullopt , true , { } , { } } ; // occurs check failed
Subtyping subtyping { ctx - > builtins , ctx - > arena , ctx - > normalizer , ctx - > ice , ctx - > scope } ;
if ( ! subtyping . isSubtype ( inferredArgPack , instantiatedMmFtv - > argTypes ) . isSubtype ) // TODO: is this the right variance?
return { std : : nullopt , true , { } , { } } ;
// `len` must return a `number`.
return { ctx - > builtins - > numberType , false , { } , { } } ;
}
TypeFamilyReductionResult < TypeId > unmFamilyFn (
2024-02-16 02:04:39 +00:00
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-21 02:10:30 +01:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " unm type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId operandTy = follow ( typeParams . at ( 0 ) ) ;
2023-10-27 22:18:41 +01:00
// check to see if the operand type is resolved enough, and wait to reduce if not
if ( isPending ( operandTy , ctx - > solver ) )
return { std : : nullopt , false , { operandTy } , { } } ;
2023-10-21 02:10:30 +01:00
const NormalizedType * normTy = ctx - > normalizer - > normalize ( operandTy ) ;
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normTy )
return { std : : nullopt , false , { } , { } } ;
// if the operand is error suppressing, we can just go ahead and reduce.
if ( normTy - > shouldSuppressErrors ( ) )
return { operandTy , false , { } , { } } ;
// if we have a `never`, we can never observe that the operation didn't work.
if ( is < NeverType > ( operandTy ) )
return { ctx - > builtins - > neverType , false , { } , { } } ;
// If the type is exactly `number`, we can reduce now.
if ( normTy - > isExactlyNumber ( ) )
return { ctx - > builtins - > numberType , false , { } , { } } ;
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , operandTy , " __unm " , Location { } ) ;
if ( ! mmType )
return { std : : nullopt , true , { } , { } } ;
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
const FunctionType * mmFtv = get < FunctionType > ( * mmType ) ;
if ( ! mmFtv )
return { std : : nullopt , true , { } , { } } ;
std : : optional < TypeId > instantiatedMmType = instantiate ( ctx - > builtins , ctx - > arena , ctx - > limits , ctx - > scope , * mmType ) ;
if ( ! instantiatedMmType )
return { std : : nullopt , true , { } , { } } ;
const FunctionType * instantiatedMmFtv = get < FunctionType > ( * instantiatedMmType ) ;
if ( ! instantiatedMmFtv )
return { ctx - > builtins - > errorRecoveryType ( ) , false , { } , { } } ;
TypePackId inferredArgPack = ctx - > arena - > addTypePack ( { operandTy } ) ;
Unifier2 u2 { ctx - > arena , ctx - > builtins , ctx - > scope , ctx - > ice } ;
if ( ! u2 . unify ( inferredArgPack , instantiatedMmFtv - > argTypes ) )
return { std : : nullopt , true , { } , { } } ; // occurs check failed
Subtyping subtyping { ctx - > builtins , ctx - > arena , ctx - > normalizer , ctx - > ice , ctx - > scope } ;
if ( ! subtyping . isSubtype ( inferredArgPack , instantiatedMmFtv - > argTypes ) . isSubtype ) // TODO: is this the right variance?
return { std : : nullopt , true , { } , { } } ;
if ( std : : optional < TypeId > ret = first ( instantiatedMmFtv - > retTypes ) )
return { * ret , false , { } , { } } ;
else
return { std : : nullopt , true , { } , { } } ;
}
2024-03-09 00:47:53 +00:00
NotNull < Constraint > TypeFamilyContext : : pushConstraint ( ConstraintV & & c )
{
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-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > numericBinopFamilyFn ( TypeId instance , const std : : vector < TypeId > & typeParams ,
const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx , const std : : string metamethod )
2023-05-19 20:37:30 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
2023-10-06 20:02:32 +01:00
ctx - > ice - > ice ( " encountered a type family instance without the required argument structure " ) ;
2023-05-19 20:37:30 +01:00
LUAU_ASSERT ( false ) ;
}
2023-10-06 20:02:32 +01:00
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2023-10-27 22:18:41 +01:00
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 } , { } } ;
2023-10-06 20:02:32 +01:00
const NormalizedType * normLhsTy = ctx - > normalizer - > normalize ( lhsTy ) ;
const NormalizedType * normRhsTy = ctx - > normalizer - > normalize ( rhsTy ) ;
2023-10-13 21:20:12 +01:00
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
2023-06-02 20:52:15 +01:00
if ( ! normLhsTy | | ! normRhsTy )
return { std : : nullopt , false , { } , { } } ;
2023-10-13 21:20:12 +01:00
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
if ( normLhsTy - > shouldSuppressErrors ( ) | | normRhsTy - > shouldSuppressErrors ( ) )
2023-10-06 20:02:32 +01:00
return { ctx - > builtins - > anyType , false , { } , { } } ;
2023-10-13 21:20:12 +01:00
// if we have a `never`, we can never observe that the numeric operator didn't work.
if ( is < NeverType > ( lhsTy ) | | is < NeverType > ( rhsTy ) )
2023-10-06 20:02:32 +01:00
return { ctx - > builtins - > neverType , false , { } , { } } ;
2023-10-13 21:20:12 +01:00
// if we're adding two `number` types, the result is `number`.
if ( normLhsTy - > isExactlyNumber ( ) & & normRhsTy - > isExactlyNumber ( ) )
return { ctx - > builtins - > numberType , false , { } , { } } ;
2023-05-19 20:37:30 +01:00
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
2024-03-09 00:47:53 +00:00
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , lhsTy , metamethod , location ) ;
2023-05-19 20:37:30 +01:00
bool reversed = false ;
2023-10-06 20:02:32 +01:00
if ( ! mmType )
2023-05-19 20:37:30 +01:00
{
2024-03-09 00:47:53 +00:00
mmType = findMetatableEntry ( ctx - > builtins , dummy , rhsTy , metamethod , location ) ;
2023-05-19 20:37:30 +01:00
reversed = true ;
}
2023-10-06 20:02:32 +01:00
if ( ! mmType )
2023-05-19 20:37:30 +01:00
return { std : : nullopt , true , { } , { } } ;
2023-10-06 20:02:32 +01:00
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
2023-05-19 20:37:30 +01:00
2024-03-09 00:47:53 +00:00
TypePackId argPack = ctx - > arena - > addTypePack ( { lhsTy , rhsTy } ) ;
SolveResult solveResult ;
2023-05-19 20:37:30 +01:00
2023-10-13 21:20:12 +01:00
if ( ! reversed )
2024-03-09 00:47:53 +00:00
solveResult = solveFunctionCall ( ctx - > arena , ctx - > builtins , ctx - > normalizer , ctx - > ice , ctx - > limits , ctx - > scope , location , * mmType , argPack ) ;
2023-10-13 21:20:12 +01:00
else
2024-03-09 00:47:53 +00:00
{
TypePack * p = getMutable < TypePack > ( argPack ) ;
std : : swap ( p - > head . front ( ) , p - > head . back ( ) ) ;
solveResult = solveFunctionCall ( ctx - > arena , ctx - > builtins , ctx - > normalizer , ctx - > ice , ctx - > limits , ctx - > scope , location , * mmType , argPack ) ;
}
2023-10-13 21:20:12 +01:00
2024-03-09 00:47:53 +00:00
if ( ! solveResult . typePackId . has_value ( ) )
return { std : : nullopt , true , { } , { } } ;
2023-10-13 21:20:12 +01:00
2024-03-09 00:47:53 +00:00
TypePack extracted = extendTypePack ( * ctx - > arena , ctx - > builtins , * solveResult . typePackId , 1 ) ;
if ( extracted . head . empty ( ) )
2023-05-19 20:37:30 +01:00
return { std : : nullopt , true , { } , { } } ;
2024-03-09 00:47:53 +00:00
return { extracted . head . front ( ) , false , { } , { } } ;
2023-05-19 20:37:30 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > addFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " add type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __add " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > subFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " sub type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __sub " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > mulFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " mul type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __mul " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > divFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " div type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __div " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > idivFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " integer div type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __idiv " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > powFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " pow type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __pow " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > modFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " modulo type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return numericBinopFamilyFn ( instance , typeParams , packParams , ctx , " __mod " ) ;
2023-10-06 20:02:32 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > concatFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " concat type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2023-10-27 22:18:41 +01:00
// check to see if both operand types are resolved enough, and wait to reduce if not
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
2023-10-13 21:20:12 +01:00
const NormalizedType * normLhsTy = ctx - > normalizer - > normalize ( lhsTy ) ;
const NormalizedType * normRhsTy = ctx - > normalizer - > normalize ( rhsTy ) ;
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normLhsTy | | ! normRhsTy )
return { std : : nullopt , false , { } , { } } ;
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
if ( normLhsTy - > shouldSuppressErrors ( ) | | normRhsTy - > shouldSuppressErrors ( ) )
return { ctx - > builtins - > anyType , false , { } , { } } ;
// if we have a `never`, we can never observe that the numeric operator didn't work.
if ( is < NeverType > ( lhsTy ) | | is < NeverType > ( rhsTy ) )
return { ctx - > builtins - > neverType , false , { } , { } } ;
// if we're concatenating two elements that are either strings or numbers, the result is `string`.
if ( ( normLhsTy - > isSubtypeOfString ( ) | | normLhsTy - > isExactlyNumber ( ) ) & & ( normRhsTy - > isSubtypeOfString ( ) | | normRhsTy - > isExactlyNumber ( ) ) )
return { ctx - > builtins - > stringType , false , { } , { } } ;
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , lhsTy , " __concat " , Location { } ) ;
bool reversed = false ;
if ( ! mmType )
{
mmType = findMetatableEntry ( ctx - > builtins , dummy , rhsTy , " __concat " , Location { } ) ;
reversed = true ;
}
if ( ! mmType )
return { std : : nullopt , true , { } , { } } ;
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
const FunctionType * mmFtv = get < FunctionType > ( * mmType ) ;
if ( ! mmFtv )
return { std : : nullopt , true , { } , { } } ;
std : : optional < TypeId > instantiatedMmType = instantiate ( ctx - > builtins , ctx - > arena , ctx - > limits , ctx - > scope , * mmType ) ;
if ( ! instantiatedMmType )
return { std : : nullopt , true , { } , { } } ;
const FunctionType * instantiatedMmFtv = get < FunctionType > ( * instantiatedMmType ) ;
if ( ! instantiatedMmFtv )
return { ctx - > builtins - > errorRecoveryType ( ) , false , { } , { } } ;
std : : vector < TypeId > inferredArgs ;
if ( ! reversed )
inferredArgs = { lhsTy , rhsTy } ;
else
inferredArgs = { rhsTy , lhsTy } ;
TypePackId inferredArgPack = ctx - > arena - > addTypePack ( std : : move ( inferredArgs ) ) ;
Unifier2 u2 { ctx - > arena , ctx - > builtins , ctx - > scope , ctx - > ice } ;
if ( ! u2 . unify ( inferredArgPack , instantiatedMmFtv - > argTypes ) )
return { std : : nullopt , true , { } , { } } ; // occurs check failed
Subtyping subtyping { ctx - > builtins , ctx - > arena , ctx - > normalizer , ctx - > ice , ctx - > scope } ;
if ( ! subtyping . isSubtype ( inferredArgPack , instantiatedMmFtv - > argTypes ) . isSubtype ) // TODO: is this the right variance?
return { std : : nullopt , true , { } , { } } ;
return { ctx - > builtins - > stringType , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > andFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " and type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2024-02-16 02:04:39 +00:00
// t1 = and<lhs, t1> ~> lhs
if ( follow ( rhsTy ) = = instance & & lhsTy ! = rhsTy )
return { lhsTy , false , { } , { } } ;
// t1 = and<t1, rhs> ~> rhs
if ( follow ( lhsTy ) = = instance & & lhsTy ! = rhsTy )
return { rhsTy , false , { } , { } } ;
2023-10-27 22:18:41 +01:00
// check to see if both operand types are resolved enough, and wait to reduce if not
2023-10-06 20:02:32 +01:00
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is truthy.
SimplifyResult filteredLhs = simplifyIntersection ( ctx - > builtins , ctx - > arena , lhsTy , ctx - > builtins - > falsyType ) ;
SimplifyResult overallResult = simplifyUnion ( ctx - > builtins , ctx - > arena , rhsTy , filteredLhs . result ) ;
2023-11-10 21:10:07 +00:00
std : : vector < TypeId > blockedTypes { } ;
for ( auto ty : filteredLhs . blockedTypes )
blockedTypes . push_back ( ty ) ;
for ( auto ty : overallResult . blockedTypes )
blockedTypes . push_back ( ty ) ;
2023-10-06 20:02:32 +01:00
return { overallResult . result , false , std : : move ( blockedTypes ) , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > orFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-06 20:02:32 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " or type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2024-02-16 02:04:39 +00:00
// t1 = or<lhs, t1> ~> lhs
if ( follow ( rhsTy ) = = instance & & lhsTy ! = rhsTy )
return { lhsTy , false , { } , { } } ;
// t1 = or<t1, rhs> ~> rhs
if ( follow ( lhsTy ) = = instance & & lhsTy ! = rhsTy )
return { rhsTy , false , { } , { } } ;
2023-10-27 22:18:41 +01:00
// check to see if both operand types are resolved enough, and wait to reduce if not
2023-10-06 20:02:32 +01:00
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
// Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy.
SimplifyResult filteredLhs = simplifyIntersection ( ctx - > builtins , ctx - > arena , lhsTy , ctx - > builtins - > truthyType ) ;
SimplifyResult overallResult = simplifyUnion ( ctx - > builtins , ctx - > arena , rhsTy , filteredLhs . result ) ;
2023-11-10 21:10:07 +00:00
std : : vector < TypeId > blockedTypes { } ;
for ( auto ty : filteredLhs . blockedTypes )
blockedTypes . push_back ( ty ) ;
for ( auto ty : overallResult . blockedTypes )
blockedTypes . push_back ( ty ) ;
2023-10-06 20:02:32 +01:00
return { overallResult . result , false , std : : move ( blockedTypes ) , { } } ;
}
2024-02-16 02:04:39 +00:00
static TypeFamilyReductionResult < TypeId > comparisonFamilyFn ( TypeId instance , const std : : vector < TypeId > & typeParams ,
const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx , const std : : string metamethod )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2023-10-27 22:18:41 +01:00
2024-03-09 00:47:53 +00:00
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
2024-02-23 20:08:34 +00:00
// Algebra Reduction Rules for comparison family functions
// Note that comparing to never tells you nothing about the other operand
// lt< 'a , never> -> continue
// lt< never, 'a> -> continue
// lt< 'a, t> -> 'a is t - we'll solve the constraint, return and solve lt<t, t> -> bool
// lt< t, 'a> -> same as above
bool canSubmitConstraint = ctx - > solver & & ctx - > constraint ;
2024-03-01 18:45:26 +00:00
bool lhsFree = get < FreeType > ( lhsTy ) ! = nullptr ;
bool rhsFree = get < FreeType > ( rhsTy ) ! = nullptr ;
2024-02-23 20:08:34 +00:00
if ( canSubmitConstraint )
{
2024-03-01 18:45:26 +00:00
// Implement injective type families for comparison type families
// lt <number, t> implies t is number
// lt <t, number> implies t is number
if ( lhsFree & & isNumber ( rhsTy ) )
asMutable ( lhsTy ) - > ty . emplace < BoundType > ( ctx - > builtins - > numberType ) ;
else if ( rhsFree & & isNumber ( lhsTy ) )
asMutable ( rhsTy ) - > ty . emplace < BoundType > ( ctx - > builtins - > numberType ) ;
else if ( lhsFree & & get < NeverType > ( rhsTy ) = = nullptr )
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-03-01 18:45:26 +00:00
else if ( rhsFree & & get < NeverType > ( lhsTy ) = = nullptr )
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 ) ;
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-13 21:20:12 +01:00
const NormalizedType * normLhsTy = ctx - > normalizer - > normalize ( lhsTy ) ;
const NormalizedType * normRhsTy = ctx - > normalizer - > normalize ( rhsTy ) ;
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normLhsTy | | ! normRhsTy )
return { std : : nullopt , false , { } , { } } ;
// if one of the types is error suppressing, we can just go ahead and reduce.
if ( normLhsTy - > shouldSuppressErrors ( ) | | normRhsTy - > shouldSuppressErrors ( ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// if we have a `never`, we can never observe that the comparison didn't work.
if ( is < NeverType > ( lhsTy ) | | is < NeverType > ( rhsTy ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// If both types are some strict subset of `string`, we can reduce now.
if ( normLhsTy - > isSubtypeOfString ( ) & & normRhsTy - > isSubtypeOfString ( ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// If both types are exactly `number`, we can reduce now.
if ( normLhsTy - > isExactlyNumber ( ) & & normRhsTy - > isExactlyNumber ( ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , lhsTy , metamethod , Location { } ) ;
if ( ! mmType )
mmType = findMetatableEntry ( ctx - > builtins , dummy , rhsTy , metamethod , Location { } ) ;
if ( ! mmType )
return { std : : nullopt , true , { } , { } } ;
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
const FunctionType * mmFtv = get < FunctionType > ( * mmType ) ;
if ( ! mmFtv )
return { std : : nullopt , true , { } , { } } ;
std : : optional < TypeId > instantiatedMmType = instantiate ( ctx - > builtins , ctx - > arena , ctx - > limits , ctx - > scope , * mmType ) ;
if ( ! instantiatedMmType )
return { std : : nullopt , true , { } , { } } ;
const FunctionType * instantiatedMmFtv = get < FunctionType > ( * instantiatedMmType ) ;
if ( ! instantiatedMmFtv )
return { ctx - > builtins - > errorRecoveryType ( ) , false , { } , { } } ;
TypePackId inferredArgPack = ctx - > arena - > addTypePack ( { lhsTy , rhsTy } ) ;
Unifier2 u2 { ctx - > arena , ctx - > builtins , ctx - > scope , ctx - > ice } ;
if ( ! u2 . unify ( inferredArgPack , instantiatedMmFtv - > argTypes ) )
return { std : : nullopt , true , { } , { } } ; // occurs check failed
Subtyping subtyping { ctx - > builtins , ctx - > arena , ctx - > normalizer , ctx - > ice , ctx - > scope } ;
if ( ! subtyping . isSubtype ( inferredArgPack , instantiatedMmFtv - > argTypes ) . isSubtype ) // TODO: is this the right variance?
return { std : : nullopt , true , { } , { } } ;
return { ctx - > builtins - > booleanType , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > ltFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " lt type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return comparisonFamilyFn ( instance , typeParams , packParams , ctx , " __lt " ) ;
2023-10-13 21:20:12 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > leFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " le type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
2024-02-16 02:04:39 +00:00
return comparisonFamilyFn ( instance , typeParams , packParams , ctx , " __le " ) ;
2023-10-13 21:20:12 +01:00
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > eqFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-10-13 21:20:12 +01:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " eq type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
2023-10-27 22:18:41 +01:00
// check to see if both operand types are resolved enough, and wait to reduce if not
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
2023-10-13 21:20:12 +01:00
const NormalizedType * normLhsTy = ctx - > normalizer - > normalize ( lhsTy ) ;
const NormalizedType * normRhsTy = ctx - > normalizer - > normalize ( rhsTy ) ;
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normLhsTy | | ! normRhsTy )
return { std : : nullopt , false , { } , { } } ;
// if one of the types is error suppressing, we can just go ahead and reduce.
if ( normLhsTy - > shouldSuppressErrors ( ) | | normRhsTy - > shouldSuppressErrors ( ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// if we have a `never`, we can never observe that the comparison didn't work.
if ( is < NeverType > ( lhsTy ) | | is < NeverType > ( rhsTy ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ;
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , lhsTy , " __eq " , Location { } ) ;
if ( ! mmType )
mmType = findMetatableEntry ( ctx - > builtins , dummy , rhsTy , " __eq " , Location { } ) ;
// if neither type has a metatable entry for `__eq`, then we'll check for inhabitance of the intersection!
if ( ! mmType & & ctx - > normalizer - > isIntersectionInhabited ( lhsTy , rhsTy ) )
return { ctx - > builtins - > booleanType , false , { } , { } } ; // if it's inhabited, everything is okay!
else if ( ! mmType )
return { std : : nullopt , true , { } , { } } ; // if it's not, then this family is irreducible!
mmType = follow ( * mmType ) ;
if ( isPending ( * mmType , ctx - > solver ) )
return { std : : nullopt , false , { * mmType } , { } } ;
const FunctionType * mmFtv = get < FunctionType > ( * mmType ) ;
if ( ! mmFtv )
return { std : : nullopt , true , { } , { } } ;
std : : optional < TypeId > instantiatedMmType = instantiate ( ctx - > builtins , ctx - > arena , ctx - > limits , ctx - > scope , * mmType ) ;
if ( ! instantiatedMmType )
return { std : : nullopt , true , { } , { } } ;
const FunctionType * instantiatedMmFtv = get < FunctionType > ( * instantiatedMmType ) ;
if ( ! instantiatedMmFtv )
return { ctx - > builtins - > errorRecoveryType ( ) , false , { } , { } } ;
TypePackId inferredArgPack = ctx - > arena - > addTypePack ( { lhsTy , rhsTy } ) ;
Unifier2 u2 { ctx - > arena , ctx - > builtins , ctx - > scope , ctx - > ice } ;
if ( ! u2 . unify ( inferredArgPack , instantiatedMmFtv - > argTypes ) )
return { std : : nullopt , true , { } , { } } ; // occurs check failed
Subtyping subtyping { ctx - > builtins , ctx - > arena , ctx - > normalizer , ctx - > ice , ctx - > scope } ;
if ( ! subtyping . isSubtype ( inferredArgPack , instantiatedMmFtv - > argTypes ) . isSubtype ) // TODO: is this the right variance?
return { std : : nullopt , true , { } , { } } ;
return { ctx - > builtins - > booleanType , false , { } , { } } ;
}
2023-12-15 21:29:06 +00:00
// Collect types that prevent us from reducing a particular refinement.
struct FindRefinementBlockers : TypeOnceVisitor
{
DenseHashSet < TypeId > found { nullptr } ;
bool visit ( TypeId ty , const BlockedType & ) override
{
found . insert ( ty ) ;
return false ;
}
bool visit ( TypeId ty , const PendingExpansionType & ) override
{
found . insert ( ty ) ;
return false ;
}
bool visit ( TypeId ty , const LocalType & ) override
{
found . insert ( ty ) ;
return false ;
}
bool visit ( TypeId ty , const ClassType & ) override
{
return false ;
}
} ;
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > refineFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2023-12-15 21:29:06 +00:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " refine type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId targetTy = follow ( typeParams . at ( 0 ) ) ;
TypeId discriminantTy = follow ( typeParams . at ( 1 ) ) ;
// check to see if both operand types are resolved enough, and wait to reduce if not
if ( isPending ( targetTy , ctx - > solver ) )
return { std : : nullopt , false , { targetTy } , { } } ;
else if ( isPending ( discriminantTy , ctx - > solver ) )
return { std : : nullopt , false , { discriminantTy } , { } } ;
// we need a more complex check for blocking on the discriminant in particular
FindRefinementBlockers frb ;
frb . traverse ( discriminantTy ) ;
if ( ! frb . found . empty ( ) )
return { std : : nullopt , false , { frb . found . begin ( ) , frb . found . end ( ) } , { } } ;
/* HACK: Refinements sometimes produce a type T & ~any under the assumption
* that ~ any is the same as any . This is so so weird , but refinements needs
* some way to say " I may refine this, but I'm not sure. "
*
* It does this by refining on a blocked type and deferring the decision
* until it is unblocked .
*
* Refinements also get negated , so we wind up with types like T & ~ * blocked *
*
* We need to treat T & ~ any as T in this case .
*/
if ( auto nt = get < NegationType > ( discriminantTy ) )
if ( get < AnyType > ( follow ( nt - > ty ) ) )
return { targetTy , false , { } , { } } ;
TypeId intersection = ctx - > arena - > addType ( IntersectionType { { targetTy , discriminantTy } } ) ;
const NormalizedType * normIntersection = ctx - > normalizer - > normalize ( intersection ) ;
const NormalizedType * normType = ctx - > normalizer - > normalize ( targetTy ) ;
// if the intersection failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normIntersection | | ! normType )
return { std : : nullopt , false , { } , { } } ;
TypeId resultTy = ctx - > normalizer - > typeFromNormal ( * normIntersection ) ;
// include the error type if the target type is error-suppressing and the intersection we computed is not
if ( normType - > shouldSuppressErrors ( ) & & ! normIntersection - > shouldSuppressErrors ( ) )
resultTy = ctx - > arena - > addType ( UnionType { { resultTy , ctx - > builtins - > errorType } } ) ;
return { resultTy , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > unionFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2024-01-27 03:20:56 +00:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " union type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
// check to see if both operand types are resolved enough, and wait to reduce if not
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( get < NeverType > ( lhsTy ) ) // if the lhs is never, we don't need this family anymore
return { rhsTy , false , { } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
else if ( get < NeverType > ( rhsTy ) ) // if the rhs is never, we don't need this family anymore
return { lhsTy , false , { } , { } } ;
SimplifyResult result = simplifyUnion ( ctx - > builtins , ctx - > arena , lhsTy , rhsTy ) ;
if ( ! result . blockedTypes . empty ( ) )
return { std : : nullopt , false , { result . blockedTypes . begin ( ) , result . blockedTypes . end ( ) } , { } } ;
return { result . result , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > intersectFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2024-01-27 03:20:56 +00:00
{
if ( typeParams . size ( ) ! = 2 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " intersect type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId lhsTy = follow ( typeParams . at ( 0 ) ) ;
TypeId rhsTy = follow ( typeParams . at ( 1 ) ) ;
// check to see if both operand types are resolved enough, and wait to reduce if not
if ( isPending ( lhsTy , ctx - > solver ) )
return { std : : nullopt , false , { lhsTy } , { } } ;
else if ( get < NeverType > ( lhsTy ) ) // if the lhs is never, we don't need this family anymore
return { ctx - > builtins - > neverType , false , { } , { } } ;
else if ( isPending ( rhsTy , ctx - > solver ) )
return { std : : nullopt , false , { rhsTy } , { } } ;
else if ( get < NeverType > ( rhsTy ) ) // if the rhs is never, we don't need this family anymore
return { ctx - > builtins - > neverType , false , { } , { } } ;
SimplifyResult result = simplifyIntersection ( ctx - > builtins , ctx - > arena , lhsTy , rhsTy ) ;
if ( ! result . blockedTypes . empty ( ) )
return { std : : nullopt , false , { result . blockedTypes . begin ( ) , result . blockedTypes . end ( ) } , { } } ;
// if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail.
if ( get < NeverType > ( result . result ) )
{
TypeId intersection = ctx - > arena - > addType ( IntersectionType { { lhsTy , rhsTy } } ) ;
return { intersection , false , { } , { } } ;
}
return { result . result , false , { } , { } } ;
}
2024-01-12 22:25:27 +00:00
// computes the keys of `ty` into `result`
// `isRaw` parameter indicates whether or not we should follow __index metamethods
// returns `false` if `result` should be ignored because the answer is "all strings"
2024-01-19 18:04:46 +00:00
bool computeKeysOf ( TypeId ty , Set < std : : string > & result , DenseHashSet < TypeId > & seen , bool isRaw , NotNull < TypeFamilyContext > ctx )
2024-01-12 22:25:27 +00:00
{
// if the type is the top table type, the answer is just "all strings"
if ( get < PrimitiveType > ( ty ) )
return false ;
// if we've already seen this type, we can do nothing
if ( seen . contains ( ty ) )
return true ;
seen . insert ( ty ) ;
// if we have a particular table type, we can insert the keys
if ( auto tableTy = get < TableType > ( ty ) )
{
2024-01-19 18:04:46 +00:00
if ( tableTy - > indexer )
{
// if we have a string indexer, the answer is, again, "all strings"
if ( isString ( tableTy - > indexer - > indexType ) )
return false ;
}
2024-01-12 22:25:27 +00:00
for ( auto [ key , _ ] : tableTy - > props )
result . insert ( key ) ;
return true ;
}
// otherwise, we have a metatable to deal with
if ( auto metatableTy = get < MetatableType > ( ty ) )
{
bool res = true ;
if ( ! isRaw )
{
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy ;
std : : optional < TypeId > mmType = findMetatableEntry ( ctx - > builtins , dummy , ty , " __index " , Location { } ) ;
if ( mmType )
res = res & & computeKeysOf ( * mmType , result , seen , isRaw , ctx ) ;
}
res = res & & computeKeysOf ( metatableTy - > table , result , seen , isRaw , ctx ) ;
return res ;
}
// this should not be reachable since the type should be a valid tables part from normalization.
LUAU_ASSERT ( false ) ;
return false ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > keyofFamilyImpl (
const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx , bool isRaw )
2024-01-12 22:25:27 +00:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " keyof type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
TypeId operandTy = follow ( typeParams . at ( 0 ) ) ;
const NormalizedType * normTy = ctx - > normalizer - > normalize ( operandTy ) ;
// if the operand failed to normalize, we can't reduce, but know nothing about inhabitance.
if ( ! normTy )
return { std : : nullopt , false , { } , { } } ;
2024-02-23 20:08:34 +00:00
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes
// as well)
2024-01-12 22:25:27 +00:00
if ( normTy - > hasTables ( ) = = normTy - > hasClasses ( ) )
return { std : : nullopt , true , { } , { } } ;
// this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables.
if ( normTy - > hasTops ( ) | | normTy - > hasBooleans ( ) | | normTy - > hasErrors ( ) | | normTy - > hasNils ( ) | | normTy - > hasNumbers ( ) | | normTy - > hasStrings ( ) | |
normTy - > hasThreads ( ) | | normTy - > hasBuffers ( ) | | normTy - > hasFunctions ( ) | | normTy - > hasTyvars ( ) )
return { std : : nullopt , true , { } , { } } ;
// we're going to collect the keys in here
2024-01-19 18:04:46 +00:00
Set < std : : string > keys { { } } ;
2024-01-12 22:25:27 +00:00
// computing the keys for classes
if ( normTy - > hasClasses ( ) )
{
LUAU_ASSERT ( ! normTy - > hasTables ( ) ) ;
auto classesIter = normTy - > classes . ordering . begin ( ) ;
auto classesIterEnd = normTy - > classes . ordering . end ( ) ;
LUAU_ASSERT ( classesIter ! = classesIterEnd ) ; // should be guaranteed by the `hasClasses` check
auto classTy = get < ClassType > ( * classesIter ) ;
if ( ! classTy )
{
LUAU_ASSERT ( false ) ; // this should not be possible according to normalization's spec
return { std : : nullopt , true , { } , { } } ;
}
for ( auto [ key , _ ] : classTy - > props )
keys . insert ( key ) ;
2024-01-19 18:04:46 +00:00
// we need to look at each class to remove any keys that are not common amongst them all
2024-01-12 22:25:27 +00:00
while ( + + classesIter ! = classesIterEnd )
{
auto classTy = get < ClassType > ( * classesIter ) ;
if ( ! classTy )
{
LUAU_ASSERT ( false ) ; // this should not be possible according to normalization's spec
return { std : : nullopt , true , { } , { } } ;
}
2024-01-19 18:04:46 +00:00
for ( auto key : keys )
2024-01-12 22:25:27 +00:00
{
2024-01-19 18:04:46 +00:00
// remove any keys that are not present in each class
if ( classTy - > props . find ( key ) = = classTy - > props . end ( ) )
keys . erase ( key ) ;
2024-01-12 22:25:27 +00:00
}
}
}
// computing the keys for tables
if ( normTy - > hasTables ( ) )
{
LUAU_ASSERT ( ! normTy - > hasClasses ( ) ) ;
// seen set for key computation for tables
DenseHashSet < TypeId > seen { { } } ;
auto tablesIter = normTy - > tables . begin ( ) ;
LUAU_ASSERT ( tablesIter ! = normTy - > tables . end ( ) ) ; // should be guaranteed by the `hasTables` check earlier
// collect all the properties from the first table type
if ( ! computeKeysOf ( * tablesIter , keys , seen , isRaw , ctx ) )
return { ctx - > builtins - > stringType , false , { } , { } } ; // if it failed, we have the top table type!
2024-01-19 18:04:46 +00:00
// we need to look at each tables to remove any keys that are not common amongst them all
2024-01-12 22:25:27 +00:00
while ( + + tablesIter ! = normTy - > tables . end ( ) )
{
seen . clear ( ) ; // we'll reuse the same seen set
2024-01-19 18:04:46 +00:00
Set < std : : string > localKeys { { } } ;
2024-01-12 22:25:27 +00:00
2024-01-19 18:04:46 +00:00
// we can skip to the next table if this one is the top table type
2024-01-12 22:25:27 +00:00
if ( ! computeKeysOf ( * tablesIter , localKeys , seen , isRaw , ctx ) )
2024-01-19 18:04:46 +00:00
continue ;
2024-01-12 22:25:27 +00:00
2024-01-19 18:04:46 +00:00
for ( auto key : keys )
{
// remove any keys that are not present in each table
if ( ! localKeys . contains ( key ) )
keys . erase ( key ) ;
}
2024-01-12 22:25:27 +00:00
}
}
// if the set of keys is empty, `keyof<T>` is `never`
if ( keys . empty ( ) )
return { ctx - > builtins - > neverType , false , { } , { } } ;
// everything is validated, we need only construct our big union of singletons now!
std : : vector < TypeId > singletons ;
singletons . reserve ( keys . size ( ) ) ;
for ( std : : string key : keys )
singletons . push_back ( ctx - > arena - > addType ( SingletonType { StringSingleton { key } } ) ) ;
return { ctx - > arena - > addType ( UnionType { singletons } ) , false , { } , { } } ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > keyofFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2024-01-12 22:25:27 +00:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " keyof type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
return keyofFamilyImpl ( typeParams , packParams , ctx , /* isRaw */ false ) ;
}
2024-02-16 02:04:39 +00:00
TypeFamilyReductionResult < TypeId > rawkeyofFamilyFn (
TypeId instance , const std : : vector < TypeId > & typeParams , const std : : vector < TypePackId > & packParams , NotNull < TypeFamilyContext > ctx )
2024-01-12 22:25:27 +00:00
{
if ( typeParams . size ( ) ! = 1 | | ! packParams . empty ( ) )
{
ctx - > ice - > ice ( " rawkeyof type family: encountered a type family instance without the required argument structure " ) ;
LUAU_ASSERT ( false ) ;
}
return keyofFamilyImpl ( typeParams , packParams , ctx , /* isRaw */ true ) ;
}
2023-05-19 20:37:30 +01:00
BuiltinTypeFamilies : : BuiltinTypeFamilies ( )
2023-10-13 21:20:12 +01:00
: notFamily { " not " , notFamilyFn }
2023-10-21 02:10:30 +01:00
, lenFamily { " len " , lenFamilyFn }
, unmFamily { " unm " , unmFamilyFn }
2023-10-13 21:20:12 +01:00
, addFamily { " add " , addFamilyFn }
, subFamily { " sub " , subFamilyFn }
, mulFamily { " mul " , mulFamilyFn }
, divFamily { " div " , divFamilyFn }
, idivFamily { " idiv " , idivFamilyFn }
, powFamily { " pow " , powFamilyFn }
, modFamily { " mod " , modFamilyFn }
, concatFamily { " concat " , concatFamilyFn }
, andFamily { " and " , andFamilyFn }
, orFamily { " or " , orFamilyFn }
, ltFamily { " lt " , ltFamilyFn }
, leFamily { " le " , leFamilyFn }
, eqFamily { " eq " , eqFamilyFn }
2023-12-15 21:29:06 +00:00
, refineFamily { " refine " , refineFamilyFn }
2024-01-27 03:20:56 +00:00
, unionFamily { " union " , unionFamilyFn }
, intersectFamily { " intersect " , intersectFamilyFn }
2024-01-12 22:25:27 +00:00
, keyofFamily { " keyof " , keyofFamilyFn }
, rawkeyofFamily { " rawkeyof " , rawkeyofFamilyFn }
2023-10-06 20:02:32 +01:00
{
}
void BuiltinTypeFamilies : : addToScope ( NotNull < TypeArena > arena , NotNull < Scope > scope ) const
2023-05-19 20:37:30 +01:00
{
2023-10-21 02:10:30 +01:00
// make a type function for a one-argument type family
auto mkUnaryTypeFamily = [ & ] ( const TypeFamily * family ) {
TypeId t = arena - > addType ( GenericType { " T " } ) ;
GenericTypeDefinition genericT { t } ;
return TypeFun { { genericT } , arena - > addType ( TypeFamilyInstanceType { NotNull { family } , { t } , { } } ) } ;
} ;
2023-10-06 20:02:32 +01:00
// make a type function for a two-argument type family
auto mkBinaryTypeFamily = [ & ] ( const TypeFamily * family ) {
TypeId t = arena - > addType ( GenericType { " T " } ) ;
TypeId u = arena - > addType ( GenericType { " U " } ) ;
GenericTypeDefinition genericT { t } ;
2024-03-30 23:14:44 +00:00
GenericTypeDefinition genericU { u , { t } } ;
2023-10-06 20:02:32 +01:00
return TypeFun { { genericT , genericU } , arena - > addType ( TypeFamilyInstanceType { NotNull { family } , { t , u } , { } } ) } ;
} ;
2023-10-21 02:10:30 +01:00
scope - > exportedTypeBindings [ lenFamily . name ] = mkUnaryTypeFamily ( & lenFamily ) ;
scope - > exportedTypeBindings [ unmFamily . name ] = mkUnaryTypeFamily ( & unmFamily ) ;
2023-10-06 20:02:32 +01:00
scope - > exportedTypeBindings [ addFamily . name ] = mkBinaryTypeFamily ( & addFamily ) ;
scope - > exportedTypeBindings [ subFamily . name ] = mkBinaryTypeFamily ( & subFamily ) ;
scope - > exportedTypeBindings [ mulFamily . name ] = mkBinaryTypeFamily ( & mulFamily ) ;
scope - > exportedTypeBindings [ divFamily . name ] = mkBinaryTypeFamily ( & divFamily ) ;
scope - > exportedTypeBindings [ idivFamily . name ] = mkBinaryTypeFamily ( & idivFamily ) ;
scope - > exportedTypeBindings [ powFamily . name ] = mkBinaryTypeFamily ( & powFamily ) ;
scope - > exportedTypeBindings [ modFamily . name ] = mkBinaryTypeFamily ( & modFamily ) ;
2023-10-13 21:20:12 +01:00
scope - > exportedTypeBindings [ concatFamily . name ] = mkBinaryTypeFamily ( & concatFamily ) ;
scope - > exportedTypeBindings [ ltFamily . name ] = mkBinaryTypeFamily ( & ltFamily ) ;
scope - > exportedTypeBindings [ leFamily . name ] = mkBinaryTypeFamily ( & leFamily ) ;
scope - > exportedTypeBindings [ eqFamily . name ] = mkBinaryTypeFamily ( & eqFamily ) ;
2024-01-12 22:25:27 +00:00
scope - > exportedTypeBindings [ keyofFamily . name ] = mkUnaryTypeFamily ( & keyofFamily ) ;
scope - > exportedTypeBindings [ rawkeyofFamily . name ] = mkUnaryTypeFamily ( & rawkeyofFamily ) ;
2023-05-12 18:50:47 +01:00
}
} // namespace Luau