Sync to upstream/release/635 (#1337)

# What's Changed?

- Bugfixes in the new solver

## New Solver
- Equality graphs(E-Graphs) data structures were added
- Refactored even more instances of "type family" with "type function"
- `table.insert` no longer spuriously warns while selecting an overload
for reasonable arguments.
- Add time tracing for the new solver
- Miscellaneous fixes to unit tests

---
### Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Jeremy Yoo <jyoo@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: David Cope <dcope@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Junseo Yoo <jyoo@roblox.com>
This commit is contained in:
Vighnesh-V 2024-07-19 11:20:47 -07:00 committed by GitHub
parent 2874ca9e86
commit a7299c3f0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 463 additions and 384 deletions

View File

@ -371,7 +371,7 @@ private:
std::vector<std::optional<TypeId>> getExpectedCallTypesForFunctionOverloads(const TypeId fnType); std::vector<std::optional<TypeId>> getExpectedCallTypesForFunctionOverloads(const TypeId fnType);
TypeId createTypeFunctionInstance( TypeId createTypeFunctionInstance(
const TypeFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments, const ScopePtr& scope, Location location); const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments, const ScopePtr& scope, Location location);
}; };
/** Borrow a vector of pointers from a vector of owning pointers to constraints. /** Borrow a vector of pointers from a vector of owning pointers to constraints.

View File

@ -91,8 +91,8 @@ struct ConstraintSolver
// A mapping from free types to the number of unresolved constraints that mention them. // A mapping from free types to the number of unresolved constraints that mention them.
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}}; DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
// Irreducible/uninhabited type families or type pack families. // Irreducible/uninhabited type functions or type pack functions.
DenseHashSet<const void*> uninhabitedTypeFamilies{{}}; DenseHashSet<const void*> uninhabitedTypeFunctions{{}};
// The set of types that will definitely be unchanged by generalization. // The set of types that will definitely be unchanged by generalization.
DenseHashSet<TypeId> generalizedTypes_{nullptr}; DenseHashSet<TypeId> generalizedTypes_{nullptr};
@ -107,7 +107,7 @@ struct ConstraintSolver
DcrLogger* logger; DcrLogger* logger;
TypeCheckLimits limits; TypeCheckLimits limits;
DenseHashMap<TypeId, const Constraint*> typeFamiliesToFinalize{nullptr}; DenseHashMap<TypeId, const Constraint*> typeFunctionsToFinalize{nullptr};
explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints, explicit ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger, ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger,
@ -124,10 +124,10 @@ struct ConstraintSolver
/** /**
* Attempts to perform one final reduction on type families after every constraint has been completed * Attempts to perform one final reduction on type functions after every constraint has been completed
* *
**/ **/
void finalizeTypeFamilies(); void finalizeTypeFunctions();
bool isDone(); bool isDone();
@ -345,7 +345,7 @@ public:
/** /**
* Reproduces any constraints necessary for new types that are copied when applying a substitution. * Reproduces any constraints necessary for new types that are copied when applying a substitution.
* At the time of writing, this pertains only to type families. * At the time of writing, this pertains only to type functions.
* @param subst the substitution that was applied * @param subst the substitution that was applied
**/ **/
void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst); void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst);

View File

@ -85,7 +85,7 @@ struct SolveResult
DenseHashMap<TypeId, std::vector<TypeId>> expandedFreeTypes{nullptr}; DenseHashMap<TypeId, std::vector<TypeId>> expandedFreeTypes{nullptr};
}; };
// Helper utility, presently used for binary operator type families. // Helper utility, presently used for binary operator type functions.
// //
// Given a function and a set of arguments, select a suitable overload. // Given a function and a set of arguments, select a suitable overload.
SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer, SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,

View File

@ -219,8 +219,8 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFamilyInstance, const TypeId superTy); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFamilyInstance); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance);
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp); bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
@ -228,7 +228,7 @@ private:
template<typename T, typename Container> template<typename T, typename Container>
TypeId makeAggregateType(const Container& container, TypeId orElse); TypeId makeAggregateType(const Container& container, TypeId orElse);
std::pair<TypeId, ErrorVec> handleTypeFunctionReductionResult(const TypeFunctionInstanceType* familyInstance); std::pair<TypeId, ErrorVec> handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance);
[[noreturn]] void unexpected(TypeId ty); [[noreturn]] void unexpected(TypeId ty);
[[noreturn]] void unexpected(TypePackId tp); [[noreturn]] void unexpected(TypePackId tp);

View File

@ -540,27 +540,27 @@ struct ClassType
*/ */
struct TypeFunctionInstanceType struct TypeFunctionInstanceType
{ {
NotNull<const TypeFunction> family; NotNull<const TypeFunction> function;
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
TypeFunctionInstanceType(NotNull<const TypeFunction> family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypeFunctionInstanceType(NotNull<const TypeFunction> function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: family(family) : function(function)
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
{ {
} }
TypeFunctionInstanceType(const TypeFunction& family, std::vector<TypeId> typeArguments) TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments)
: family{&family} : function{&function}
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments{} , packArguments{}
{ {
} }
TypeFunctionInstanceType(const TypeFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypeFunctionInstanceType(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: family{&family} : function{&function}
, typeArguments(typeArguments) , typeArguments(typeArguments)
, packArguments(packArguments) , packArguments(packArguments)
{ {

View File

@ -49,10 +49,10 @@ struct TypeArena
return addTypePack(TypePackVar(std::move(tp))); return addTypePack(TypePackVar(std::move(tp)));
} }
TypeId addTypeFunction(const TypeFunction& family, std::initializer_list<TypeId> types); TypeId addTypeFunction(const TypeFunction& function, std::initializer_list<TypeId> types);
TypeId addTypeFunction(const TypeFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {}); TypeId addTypeFunction(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
TypePackId addTypePackFunction(const TypePackFunction& family, std::initializer_list<TypeId> types); TypePackId addTypePackFunction(const TypePackFunction& function, std::initializer_list<TypeId> types);
TypePackId addTypePackFunction(const TypePackFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {}); TypePackId addTypePackFunction(const TypePackFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
}; };
void freeze(TypeArena& arena); void freeze(TypeArena& arena);

View File

@ -29,13 +29,13 @@ struct TypeFunctionReductionGuessResult
struct TypeFunctionInferenceResult struct TypeFunctionInferenceResult
{ {
std::vector<TypeId> operandInference; std::vector<TypeId> operandInference;
TypeId familyResultInference; TypeId functionResultInference;
}; };
struct TypeFunctionReductionGuesser struct TypeFunctionReductionGuesser
{ {
// Tracks our hypothesis about what a type function reduces to // Tracks our hypothesis about what a type function reduces to
DenseHashMap<TypeId, TypeId> familyReducesTo{nullptr}; DenseHashMap<TypeId, TypeId> functionReducesTo{nullptr};
// Tracks our constraints on type function operands // Tracks our constraints on type function operands
DenseHashMap<TypeId, TypeId> substitutable{nullptr}; DenseHashMap<TypeId, TypeId> substitutable{nullptr};
// List of instances to try progress // List of instances to try progress
@ -57,14 +57,14 @@ private:
std::optional<TypeId> guessType(TypeId arg); std::optional<TypeId> guessType(TypeId arg);
void dumpGuesses(); void dumpGuesses();
bool isNumericBinopFamily(const TypeFunctionInstanceType& instance); bool isNumericBinopFunction(const TypeFunctionInstanceType& instance);
bool isComparisonFamily(const TypeFunctionInstanceType& instance); bool isComparisonFunction(const TypeFunctionInstanceType& instance);
bool isOrAndFamily(const TypeFunctionInstanceType& instance); bool isOrAndFunction(const TypeFunctionInstanceType& instance);
bool isNotFamily(const TypeFunctionInstanceType& instance); bool isNotFunction(const TypeFunctionInstanceType& instance);
bool isLenFamily(const TypeFunctionInstanceType& instance); bool isLenFunction(const TypeFunctionInstanceType& instance);
bool isUnaryMinus(const TypeFunctionInstanceType& instance); bool isUnaryMinus(const TypeFunctionInstanceType& instance);
// Operand is assignable if it looks like a cyclic family instance, or a generic type // Operand is assignable if it looks like a cyclic type function instance, or a generic type
bool operandIsAssignable(TypeId ty); bool operandIsAssignable(TypeId ty);
std::optional<TypeId> tryAssignOperandType(TypeId ty); std::optional<TypeId> tryAssignOperandType(TypeId ty);
@ -75,11 +75,11 @@ private:
bool isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet<TypeId>& instanceArgs); bool isFunctionGenericsSaturated(const FunctionType& ftv, DenseHashSet<TypeId>& instanceArgs);
void inferTypeFunctionSubstitutions(TypeId ty, const TypeFunctionInstanceType* instance); void inferTypeFunctionSubstitutions(TypeId ty, const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferNumericBinopFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferNumericBinopFunction(const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferComparisonFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferComparisonFunction(const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferOrAndFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferOrAndFunction(const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferNotFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferNotFunction(const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferLenFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferLenFunction(const TypeFunctionInstanceType* instance);
TypeFunctionInferenceResult inferUnaryMinusFamily(const TypeFunctionInstanceType* instance); TypeFunctionInferenceResult inferUnaryMinusFunction(const TypeFunctionInstanceType* instance);
}; };
} // namespace Luau } // namespace Luau

View File

@ -92,7 +92,7 @@ struct BlockedTypePack
*/ */
struct TypeFunctionInstanceTypePack struct TypeFunctionInstanceTypePack
{ {
NotNull<const TypePackFunction> family; NotNull<const TypePackFunction> function;
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;

View File

@ -49,11 +49,11 @@ struct Unifier2
std::vector<ConstraintV> incompleteSubtypes; std::vector<ConstraintV> incompleteSubtypes;
// null if not in a constraint solving context // null if not in a constraint solving context
DenseHashSet<const void*>* uninhabitedTypeFamilies; DenseHashSet<const void*>* uninhabitedTypeFunctions;
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice); Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice);
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice, Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice,
DenseHashSet<const void*>* uninhabitedTypeFamilies); DenseHashSet<const void*>* uninhabitedTypeFunctions);
/** Attempt to commit the subtype relation subTy <: superTy to the type /** Attempt to commit the subtype relation subTy <: superTy to the type
* graph. * graph.

View File

@ -15,6 +15,7 @@
#include "Luau/Simplify.h" #include "Luau/Simplify.h"
#include "Luau/StringUtils.h" #include "Luau/StringUtils.h"
#include "Luau/TableLiteralInference.h" #include "Luau/TableLiteralInference.h"
#include "Luau/TimeTrace.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/TypePack.h" #include "Luau/TypePack.h"
@ -211,6 +212,8 @@ ConstraintGenerator::ConstraintGenerator(ModulePtr module, NotNull<Normalizer> n
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
{ {
LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking");
LUAU_ASSERT(scopes.empty()); LUAU_ASSERT(scopes.empty());
LUAU_ASSERT(rootScope == nullptr); LUAU_ASSERT(rootScope == nullptr);
ScopePtr scope = std::make_shared<Scope>(globalScope); ScopePtr scope = std::make_shared<Scope>(globalScope);
@ -3356,9 +3359,9 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
} }
TypeId ConstraintGenerator::createTypeFunctionInstance( TypeId ConstraintGenerator::createTypeFunctionInstance(
const TypeFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments, const ScopePtr& scope, Location location) const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments, const ScopePtr& scope, Location location)
{ {
TypeId result = arena->addTypeFunction(family, typeArguments, packArguments); TypeId result = arena->addTypeFunction(function, typeArguments, packArguments);
addConstraint(scope, location, ReduceConstraint{result}); addConstraint(scope, location, ReduceConstraint{result});
return result; return result;
} }

View File

@ -366,6 +366,8 @@ void ConstraintSolver::randomize(unsigned seed)
void ConstraintSolver::run() void ConstraintSolver::run()
{ {
LUAU_TIMETRACE_SCOPE("ConstraintSolver::run", "Typechecking");
if (isDone()) if (isDone())
return; return;
@ -489,11 +491,11 @@ void ConstraintSolver::run()
if (!unsolvedConstraints.empty()) if (!unsolvedConstraints.empty())
reportError(InternalError{"Type inference failed to complete, you may see some confusing types and type errors."}, Location{}); reportError(InternalError{"Type inference failed to complete, you may see some confusing types and type errors."}, Location{});
// After we have run all the constraints, type families should be generalized // After we have run all the constraints, type functions should be generalized
// At this point, we can try to perform one final simplification to suss out // At this point, we can try to perform one final simplification to suss out
// whether type families are truly uninhabited or if they can reduce // whether type functions are truly uninhabited or if they can reduce
finalizeTypeFamilies(); finalizeTypeFunctions();
if (FFlag::DebugLuauLogSolver || FFlag::DebugLuauLogBindings) if (FFlag::DebugLuauLogSolver || FFlag::DebugLuauLogBindings)
dumpBindings(rootScope, opts); dumpBindings(rootScope, opts);
@ -504,10 +506,10 @@ void ConstraintSolver::run()
} }
} }
void ConstraintSolver::finalizeTypeFamilies() void ConstraintSolver::finalizeTypeFunctions()
{ {
// At this point, we've generalized. Let's try to finish reducing as much as we can, we'll leave warning to the typechecker // At this point, we've generalized. Let's try to finish reducing as much as we can, we'll leave warning to the typechecker
for (auto [t, constraint] : typeFamiliesToFinalize) for (auto [t, constraint] : typeFunctionsToFinalize)
{ {
TypeId ty = follow(t); TypeId ty = follow(t);
if (get<TypeFunctionInstanceType>(ty)) if (get<TypeFunctionInstanceType>(ty))
@ -721,7 +723,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
* to figure out which of the above shapes we are actually working with. * to figure out which of the above shapes we are actually working with.
* *
* If `force` is true and we still do not know, we must flag a warning. Type * If `force` is true and we still do not know, we must flag a warning. Type
* families are the fix for this. * functions are the fix for this.
* *
* Since we need to know all of this stuff about the types of the iteratee, * Since we need to know all of this stuff about the types of the iteratee,
* we have no choice but for ConstraintSolver to also be the thing that * we have no choice but for ConstraintSolver to also be the thing that
@ -1294,7 +1296,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
ftv = get<FunctionType>(*res); ftv = get<FunctionType>(*res);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
// we've potentially copied type families here, so we need to reproduce their reduce constraint. // we've potentially copied type functions here, so we need to reproduce their reduce constraint.
reproduceConstraints(constraint->scope, constraint->location, replacer); reproduceConstraints(constraint->scope, constraint->location, replacer);
} }
} }
@ -1975,17 +1977,17 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
ty = follow(ty); ty = follow(ty);
// If we couldn't reduce this type function, stick it in the set! // If we couldn't reduce this type function, stick it in the set!
if (get<TypeFunctionInstanceType>(ty)) if (get<TypeFunctionInstanceType>(ty))
typeFamiliesToFinalize[ty] = constraint; typeFunctionsToFinalize[ty] = constraint;
if (force || reductionFinished) if (force || reductionFinished)
{ {
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock. // if we're completely dispatching this constraint, we want to record any uninhabited type functions to unblock.
for (auto error : result.errors) for (auto error : result.errors)
{ {
if (auto utf = get<UninhabitedTypeFunction>(error)) if (auto utf = get<UninhabitedTypeFunction>(error))
uninhabitedTypeFamilies.insert(utf->ty); uninhabitedTypeFunctions.insert(utf->ty);
else if (auto utpf = get<UninhabitedTypePackFunction>(error)) else if (auto utpf = get<UninhabitedTypePackFunction>(error))
uninhabitedTypeFamilies.insert(utpf->tp); uninhabitedTypeFunctions.insert(utpf->tp);
} }
} }
@ -2017,13 +2019,13 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
if (force || reductionFinished) if (force || reductionFinished)
{ {
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock. // if we're completely dispatching this constraint, we want to record any uninhabited type functions to unblock.
for (auto error : result.errors) for (auto error : result.errors)
{ {
if (auto utf = get<UninhabitedTypeFunction>(error)) if (auto utf = get<UninhabitedTypeFunction>(error))
uninhabitedTypeFamilies.insert(utf->ty); uninhabitedTypeFunctions.insert(utf->ty);
else if (auto utpf = get<UninhabitedTypePackFunction>(error)) else if (auto utpf = get<UninhabitedTypePackFunction>(error))
uninhabitedTypeFamilies.insert(utpf->tp); uninhabitedTypeFunctions.insert(utpf->tp);
} }
} }
@ -2498,7 +2500,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
template<typename TID> template<typename TID>
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy) bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
{ {
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFamilies}; Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFunctions};
const bool ok = u2.unify(subTy, superTy); const bool ok = u2.unify(subTy, superTy);
@ -2735,7 +2737,7 @@ bool ConstraintSolver::isBlocked(TypeId ty)
ty = follow(ty); ty = follow(ty);
if (auto tfit = get<TypeFunctionInstanceType>(ty)) if (auto tfit = get<TypeFunctionInstanceType>(ty))
return uninhabitedTypeFamilies.contains(ty) == false; return uninhabitedTypeFunctions.contains(ty) == false;
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty); return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
} }
@ -2745,7 +2747,7 @@ bool ConstraintSolver::isBlocked(TypePackId tp)
tp = follow(tp); tp = follow(tp);
if (auto tfitp = get<TypeFunctionInstanceTypePack>(tp)) if (auto tfitp = get<TypeFunctionInstanceTypePack>(tp))
return uninhabitedTypeFamilies.contains(tp) == false; return uninhabitedTypeFunctions.contains(tp) == false;
return nullptr != get<BlockedTypePack>(tp); return nullptr != get<BlockedTypePack>(tp);
} }

View File

@ -5,6 +5,7 @@
#include "Luau/Def.h" #include "Luau/Def.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/TimeTrace.h"
#include <optional> #include <optional>
@ -136,6 +137,8 @@ bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalErrorReporter> handle) DataFlowGraph DataFlowGraphBuilder::build(AstStatBlock* block, NotNull<InternalErrorReporter> handle)
{ {
LUAU_TIMETRACE_SCOPE("DataFlowGraphBuilder::build", "Typechecking");
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution); LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
DataFlowGraphBuilder builder; DataFlowGraphBuilder builder;

View File

@ -585,7 +585,7 @@ struct ErrorConverter
return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function."; return "Unexpected type " + Luau::toString(e.ty) + " flagged as an uninhabited type function.";
// unary operators // unary operators
if (auto unaryString = kUnaryOps.find(tfit->family->name); unaryString != kUnaryOps.end()) if (auto unaryString = kUnaryOps.find(tfit->function->name); unaryString != kUnaryOps.end())
{ {
std::string result = "Operator '" + std::string(unaryString->second) + "' could not be applied to "; std::string result = "Operator '" + std::string(unaryString->second) + "' could not be applied to ";
@ -593,8 +593,8 @@ struct ErrorConverter
{ {
result += "operand of type " + Luau::toString(tfit->typeArguments[0]); result += "operand of type " + Luau::toString(tfit->typeArguments[0]);
if (tfit->family->name != "not") if (tfit->function->name != "not")
result += "; there is no corresponding overload for __" + tfit->family->name; result += "; there is no corresponding overload for __" + tfit->function->name;
} }
else else
{ {
@ -619,7 +619,7 @@ struct ErrorConverter
} }
// binary operators // binary operators
if (auto binaryString = kBinaryOps.find(tfit->family->name); binaryString != kBinaryOps.end()) if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end())
{ {
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -646,14 +646,14 @@ struct ErrorConverter
result += ", " + Luau::toString(packArg); result += ", " + Luau::toString(packArg);
} }
result += "; there is no corresponding overload for __" + tfit->family->name; result += "; there is no corresponding overload for __" + tfit->function->name;
return result; return result;
} }
// miscellaneous // miscellaneous
if ("keyof" == tfit->family->name || "rawkeyof" == tfit->family->name) if ("keyof" == tfit->function->name || "rawkeyof" == tfit->function->name)
{ {
if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty()) if (tfit->typeArguments.size() == 1 && tfit->packArguments.empty())
return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid"; return "Type '" + toString(tfit->typeArguments[0]) + "' does not have keys, so '" + Luau::toString(e.ty) + "' is invalid";
@ -661,19 +661,19 @@ struct ErrorConverter
return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid";
} }
if ("index" == tfit->family->name || "rawget" == tfit->family->name) if ("index" == tfit->function->name || "rawget" == tfit->function->name)
{ {
if (tfit->typeArguments.size() != 2) if (tfit->typeArguments.size() != 2)
return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid";
if (auto errType = get<ErrorType>(tfit->typeArguments[1])) // Second argument to (index | rawget)<_,_> is not a type if (auto errType = get<ErrorType>(tfit->typeArguments[1])) // Second argument to (index | rawget)<_,_> is not a type
return "Second argument to " + tfit->family->name + "<" + Luau::toString(tfit->typeArguments[0]) + ", _> is not a valid index type"; return "Second argument to " + tfit->function->name + "<" + Luau::toString(tfit->typeArguments[0]) + ", _> is not a valid index type";
else // Property `indexer` does not exist on type `indexee` else // Property `indexer` does not exist on type `indexee`
return "Property '" + Luau::toString(tfit->typeArguments[1]) + "' does not exist on type '" + Luau::toString(tfit->typeArguments[0]) + return "Property '" + Luau::toString(tfit->typeArguments[1]) + "' does not exist on type '" + Luau::toString(tfit->typeArguments[0]) +
"'"; "'";
} }
if (kUnreachableTypeFunctions.count(tfit->family->name)) if (kUnreachableTypeFunctions.count(tfit->function->name))
{ {
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
@ -706,7 +706,7 @@ struct ErrorConverter
std::string operator()(const UninhabitedTypePackFunction& e) const std::string operator()(const UninhabitedTypePackFunction& e) const
{ {
return "Type pack family instance " + Luau::toString(e.tp) + " is uninhabited"; return "Type pack function instance " + Luau::toString(e.tp) + " is uninhabited";
} }
std::string operator()(const WhereClauseNeeded& e) const std::string operator()(const WhereClauseNeeded& e) const
@ -718,7 +718,7 @@ struct ErrorConverter
std::string operator()(const PackWhereClauseNeeded& e) const std::string operator()(const PackWhereClauseNeeded& e) const
{ {
return "Type pack family instance " + Luau::toString(e.tp) + return "Type pack function instance " + Luau::toString(e.tp) +
" depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this " " depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this "
"time"; "time";
} }

View File

@ -1249,6 +1249,10 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options, const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog) TypeCheckLimits limits, bool recordJsonLog, std::function<void(const ModuleName&, std::string)> writeJsonLog)
{ {
LUAU_TIMETRACE_SCOPE("Frontend::check", "Typechecking");
LUAU_TIMETRACE_ARGUMENT("module", sourceModule.name.c_str());
LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str());
ModulePtr result = std::make_shared<Module>(); ModulePtr result = std::make_shared<Module>();
result->name = sourceModule.name; result->name = sourceModule.name;
result->humanReadableName = sourceModule.humanReadableName; result->humanReadableName = sourceModule.humanReadableName;

View File

@ -9,6 +9,7 @@
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/TimeTrace.h"
#include "Luau/TypeArena.h" #include "Luau/TypeArena.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
#include "Luau/Def.h" #include "Luau/Def.h"
@ -206,7 +207,7 @@ struct NonStrictTypeChecker
} }
TypeId checkForFamilyInhabitance(TypeId instance, Location location) TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location)
{ {
if (noTypeFunctionErrors.find(instance)) if (noTypeFunctionErrors.find(instance))
return instance; return instance;
@ -227,11 +228,11 @@ struct NonStrictTypeChecker
{ {
TypeId* ty = module->astTypes.find(expr); TypeId* ty = module->astTypes.find(expr);
if (ty) if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location); return checkForTypeFunctionInhabitance(follow(*ty), expr->location);
TypePackId* tp = module->astTypePacks.find(expr); TypePackId* tp = module->astTypePacks.find(expr);
if (tp) if (tp)
return checkForFamilyInhabitance(flattenPack(*tp), expr->location); return checkForTypeFunctionInhabitance(flattenPack(*tp), expr->location);
return builtinTypes->anyType; return builtinTypes->anyType;
} }
@ -728,6 +729,8 @@ private:
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState, void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,
NotNull<const DataFlowGraph> dfg, NotNull<TypeCheckLimits> limits, const SourceModule& sourceModule, Module* module) NotNull<const DataFlowGraph> dfg, NotNull<TypeCheckLimits> limits, const SourceModule& sourceModule, Module* module)
{ {
LUAU_TIMETRACE_SCOPE("checkNonStrict", "Typechecking");
NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, ice, unifierState, dfg, limits, module}; NonStrictTypeChecker typeChecker{NotNull{&module->internalTypes}, builtinTypes, ice, unifierState, dfg, limits, module};
typeChecker.visit(sourceModule.root); typeChecker.visit(sourceModule.root);
unfreeze(module->interfaceTypes); unfreeze(module->interfaceTypes);

View File

@ -127,7 +127,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
return dest.addType(NegationType{a.ty}); return dest.addType(NegationType{a.ty});
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
{ {
TypeFunctionInstanceType clone{a.family, a.typeArguments, a.packArguments}; TypeFunctionInstanceType clone{a.function, a.typeArguments, a.packArguments};
return dest.addType(std::move(clone)); return dest.addType(std::move(clone));
} }
else else
@ -672,7 +672,7 @@ TypePackId Substitution::clone(TypePackId tp)
else if (const TypeFunctionInstanceTypePack* tfitp = get<TypeFunctionInstanceTypePack>(tp)) else if (const TypeFunctionInstanceTypePack* tfitp = get<TypeFunctionInstanceTypePack>(tp))
{ {
TypeFunctionInstanceTypePack clone{ TypeFunctionInstanceTypePack clone{
tfitp->family, std::vector<TypeId>(tfitp->typeArguments.size()), std::vector<TypePackId>(tfitp->packArguments.size())}; tfitp->function, std::vector<TypeId>(tfitp->typeArguments.size()), std::vector<TypePackId>(tfitp->packArguments.size())};
clone.typeArguments.assign(tfitp->typeArguments.begin(), tfitp->typeArguments.end()); clone.typeArguments.assign(tfitp->typeArguments.begin(), tfitp->typeArguments.end());
clone.packArguments.assign(tfitp->packArguments.begin(), tfitp->packArguments.end()); clone.packArguments.assign(tfitp->packArguments.begin(), tfitp->packArguments.end());
return addTypePack(std::move(clone)); return addTypePack(std::move(clone));

View File

@ -530,6 +530,11 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
} }
else if (get<AnyType>(superTy)) else if (get<AnyType>(superTy))
result = {true}; result = {true};
// We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
// inhabitants). any = err | unknown, so under semantic subtyping, {} U unknown = unknown
else if (get<AnyType>(subTy) && get<UnknownType>(superTy))
result = {true};
else if (get<AnyType>(subTy)) else if (get<AnyType>(subTy))
{ {
// any = unknown | error, so we rewrite this to match. // any = unknown | error, so we rewrite this to match.
@ -1584,19 +1589,19 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId supe
return true; return true;
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFamilyInstance, const TypeId superTy) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy)
{ {
// Reduce the type function instance // Reduce the type function instance
auto [ty, errors] = handleTypeFunctionReductionResult(subFamilyInstance); auto [ty, errors] = handleTypeFunctionReductionResult(subFunctionInstance);
// If we return optional, that means the type function was irreducible - we can reduce that to never // If we return optional, that means the type function was irreducible - we can reduce that to never
return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty}); return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFamilyInstance) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance)
{ {
// Reduce the type function instance // Reduce the type function instance
auto [ty, errors] = handleTypeFunctionReductionResult(superFamilyInstance); auto [ty, errors] = handleTypeFunctionReductionResult(superFunctionInstance);
return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
} }
@ -1632,19 +1637,19 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))}); return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
} }
std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* familyInstance) std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance)
{ {
TypeFunctionContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}}; TypeFunctionContext context{arena, builtinTypes, scope, normalizer, iceReporter, NotNull{&limits}};
TypeId family = arena->addType(*familyInstance); TypeId function = arena->addType(*functionInstance);
FunctionGraphReductionResult result = reduceTypeFunctions(family, {}, context, true); FunctionGraphReductionResult result = reduceTypeFunctions(function, {}, context, true);
ErrorVec errors; ErrorVec errors;
if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0) if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0)
{ {
errors.push_back(TypeError{{}, UninhabitedTypeFunction{family}}); errors.push_back(TypeError{{}, UninhabitedTypeFunction{function}});
return {builtinTypes->neverType, errors}; return {builtinTypes->neverType, errors};
} }
if (result.reducedTypes.contains(family)) if (result.reducedTypes.contains(function))
return {family, errors}; return {function, errors};
return {builtinTypes->neverType, errors}; return {builtinTypes->neverType, errors};
} }

View File

@ -345,7 +345,7 @@ void StateDot::visitChildren(TypeId ty, int index)
} }
else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>) else if constexpr (std::is_same_v<T, TypeFunctionInstanceType>)
{ {
formatAppend(result, "TypeFunctionInstanceType %s %d", t.family->name.c_str(), index); formatAppend(result, "TypeFunctionInstanceType %s %d", t.function->name.c_str(), index);
finishNodeLabel(ty); finishNodeLabel(ty);
finishNode(); finishNode();

View File

@ -1033,7 +1033,7 @@ struct TypeStringifier
void operator()(TypeId, const TypeFunctionInstanceType& tfitv) void operator()(TypeId, const TypeFunctionInstanceType& tfitv)
{ {
state.emit(tfitv.family->name); state.emit(tfitv.function->name);
state.emit("<"); state.emit("<");
bool comma = false; bool comma = false;
@ -1234,7 +1234,7 @@ struct TypePackStringifier
void operator()(TypePackId, const TypeFunctionInstanceTypePack& tfitp) void operator()(TypePackId, const TypeFunctionInstanceTypePack& tfitp)
{ {
state.emit(tfitp.family->name); state.emit(tfitp.function->name);
state.emit("<"); state.emit("<");
bool comma = false; bool comma = false;

View File

@ -424,7 +424,7 @@ bool maybeSingleton(TypeId ty)
if (maybeSingleton(part)) // will i regret this? if (maybeSingleton(part)) // will i regret this?
return true; return true;
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(ty)) if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(ty))
if (tfit->family->name == "keyof" || tfit->family->name == "rawkeyof") if (tfit->function->name == "keyof" || tfit->function->name == "rawkeyof")
return true; return true;
return false; return false;
} }

View File

@ -94,24 +94,24 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
return allocated; return allocated;
} }
TypeId TypeArena::addTypeFunction(const TypeFunction& family, std::initializer_list<TypeId> types) TypeId TypeArena::addTypeFunction(const TypeFunction& function, std::initializer_list<TypeId> types)
{ {
return addType(TypeFunctionInstanceType{family, std::move(types)}); return addType(TypeFunctionInstanceType{function, std::move(types)});
} }
TypeId TypeArena::addTypeFunction(const TypeFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypeId TypeArena::addTypeFunction(const TypeFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
{ {
return addType(TypeFunctionInstanceType{family, std::move(typeArguments), std::move(packArguments)}); return addType(TypeFunctionInstanceType{function, std::move(typeArguments), std::move(packArguments)});
} }
TypePackId TypeArena::addTypePackFunction(const TypePackFunction& family, std::initializer_list<TypeId> types) TypePackId TypeArena::addTypePackFunction(const TypePackFunction& function, std::initializer_list<TypeId> types)
{ {
return addTypePack(TypeFunctionInstanceTypePack{NotNull{&family}, std::move(types)}); return addTypePack(TypeFunctionInstanceTypePack{NotNull{&function}, std::move(types)});
} }
TypePackId TypeArena::addTypePackFunction(const TypePackFunction& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments) TypePackId TypeArena::addTypePackFunction(const TypePackFunction& function, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
{ {
return addTypePack(TypeFunctionInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)}); return addTypePack(TypeFunctionInstanceTypePack{NotNull{&function}, std::move(typeArguments), std::move(packArguments)});
} }
void freeze(TypeArena& arena) void freeze(TypeArena& arena)

View File

@ -382,7 +382,7 @@ public:
} }
AstType* operator()(const TypeFunctionInstanceType& tfit) AstType* operator()(const TypeFunctionInstanceType& tfit)
{ {
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{tfit.family->name.c_str()}, std::nullopt, Location()); return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{tfit.function->name.c_str()}, std::nullopt, Location());
} }
private: private:
@ -456,7 +456,7 @@ public:
AstTypePack* operator()(const TypeFunctionInstanceTypePack& tfitp) const AstTypePack* operator()(const TypeFunctionInstanceTypePack& tfitp) const
{ {
return allocator->alloc<AstTypePackGeneric>(Location(), AstName(tfitp.family->name.c_str())); return allocator->alloc<AstTypePackGeneric>(Location(), AstName(tfitp.function->name.c_str()));
} }
private: private:

View File

@ -13,6 +13,7 @@
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/OverloadResolution.h" #include "Luau/OverloadResolution.h"
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/TimeTrace.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include "Luau/Type.h" #include "Luau/Type.h"
@ -95,7 +96,7 @@ static std::optional<std::string> getIdentifierOfBaseVar(AstExpr* node)
template<typename T> template<typename T>
bool areEquivalent(const T& a, const T& b) bool areEquivalent(const T& a, const T& b)
{ {
if (a.family != b.family) if (a.function != b.function)
return false; return false;
if (a.typeArguments.size() != b.typeArguments.size() || a.packArguments.size() != b.packArguments.size()) if (a.typeArguments.size() != b.typeArguments.size() || a.packArguments.size() != b.packArguments.size())
@ -116,39 +117,39 @@ bool areEquivalent(const T& a, const T& b)
return true; return true;
} }
struct FamilyFinder : TypeOnceVisitor struct TypeFunctionFinder : TypeOnceVisitor
{ {
DenseHashSet<TypeId> mentionedFamilies{nullptr}; DenseHashSet<TypeId> mentionedFunctions{nullptr};
DenseHashSet<TypePackId> mentionedFamilyPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
bool visit(TypeId ty, const TypeFunctionInstanceType&) override bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{ {
mentionedFamilies.insert(ty); mentionedFunctions.insert(ty);
return true; return true;
} }
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override
{ {
mentionedFamilyPacks.insert(tp); mentionedFunctionPacks.insert(tp);
return true; return true;
} }
}; };
struct InternalFamilyFinder : TypeOnceVisitor struct InternalTypeFunctionFinder : TypeOnceVisitor
{ {
DenseHashSet<TypeId> internalFamilies{nullptr}; DenseHashSet<TypeId> internalFunctions{nullptr};
DenseHashSet<TypePackId> internalPackFamilies{nullptr}; DenseHashSet<TypePackId> internalPackFunctions{nullptr};
DenseHashSet<TypeId> mentionedFamilies{nullptr}; DenseHashSet<TypeId> mentionedFunctions{nullptr};
DenseHashSet<TypePackId> mentionedFamilyPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
InternalFamilyFinder(std::vector<TypeId>& declStack) InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
{ {
FamilyFinder f; TypeFunctionFinder f;
for (TypeId fn : declStack) for (TypeId fn : declStack)
f.traverse(fn); f.traverse(fn);
mentionedFamilies = std::move(f.mentionedFamilies); mentionedFunctions = std::move(f.mentionedFunctions);
mentionedFamilyPacks = std::move(f.mentionedFamilyPacks); mentionedFunctionPacks = std::move(f.mentionedFunctionPacks);
} }
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
@ -175,7 +176,7 @@ struct InternalFamilyFinder : TypeOnceVisitor
if (hasGeneric) if (hasGeneric)
{ {
for (TypeId mentioned : mentionedFamilies) for (TypeId mentioned : mentionedFunctions)
{ {
const TypeFunctionInstanceType* mentionedTfit = get<TypeFunctionInstanceType>(mentioned); const TypeFunctionInstanceType* mentionedTfit = get<TypeFunctionInstanceType>(mentioned);
LUAU_ASSERT(mentionedTfit); LUAU_ASSERT(mentionedTfit);
@ -185,7 +186,7 @@ struct InternalFamilyFinder : TypeOnceVisitor
} }
} }
internalFamilies.insert(ty); internalFunctions.insert(ty);
} }
return true; return true;
@ -215,7 +216,7 @@ struct InternalFamilyFinder : TypeOnceVisitor
if (hasGeneric) if (hasGeneric)
{ {
for (TypePackId mentioned : mentionedFamilyPacks) for (TypePackId mentioned : mentionedFunctionPacks)
{ {
const TypeFunctionInstanceTypePack* mentionedTfitp = get<TypeFunctionInstanceTypePack>(mentioned); const TypeFunctionInstanceTypePack* mentionedTfitp = get<TypeFunctionInstanceTypePack>(mentioned);
LUAU_ASSERT(mentionedTfitp); LUAU_ASSERT(mentionedTfitp);
@ -225,7 +226,7 @@ struct InternalFamilyFinder : TypeOnceVisitor
} }
} }
internalPackFamilies.insert(tp); internalPackFunctions.insert(tp);
} }
return true; return true;
@ -423,19 +424,19 @@ struct TypeChecker2
return std::nullopt; return std::nullopt;
} }
void checkForInternalFamily(TypeId ty, Location location) void checkForInternalTypeFunction(TypeId ty, Location location)
{ {
InternalFamilyFinder finder(functionDeclStack); InternalTypeFunctionFinder finder(functionDeclStack);
finder.traverse(ty); finder.traverse(ty);
for (TypeId internal : finder.internalFamilies) for (TypeId internal : finder.internalFunctions)
reportError(WhereClauseNeeded{internal}, location); reportError(WhereClauseNeeded{internal}, location);
for (TypePackId internal : finder.internalPackFamilies) for (TypePackId internal : finder.internalPackFunctions)
reportError(PackWhereClauseNeeded{internal}, location); reportError(PackWhereClauseNeeded{internal}, location);
} }
TypeId checkForFamilyInhabitance(TypeId instance, Location location) TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location)
{ {
if (seenTypeFunctionInstances.find(instance)) if (seenTypeFunctionInstances.find(instance))
return instance; return instance;
@ -468,11 +469,11 @@ struct TypeChecker2
// allows us not to think about this very much in the actual typechecking logic. // allows us not to think about this very much in the actual typechecking logic.
TypeId* ty = module->astTypes.find(expr); TypeId* ty = module->astTypes.find(expr);
if (ty) if (ty)
return checkForFamilyInhabitance(follow(*ty), expr->location); return checkForTypeFunctionInhabitance(follow(*ty), expr->location);
TypePackId* tp = module->astTypePacks.find(expr); TypePackId* tp = module->astTypePacks.find(expr);
if (tp) if (tp)
return checkForFamilyInhabitance(flattenPack(*tp), expr->location); return checkForTypeFunctionInhabitance(flattenPack(*tp), expr->location);
return builtinTypes->anyType; return builtinTypes->anyType;
} }
@ -495,7 +496,7 @@ struct TypeChecker2
TypeId* ty = module->astResolvedTypes.find(annotation); TypeId* ty = module->astResolvedTypes.find(annotation);
LUAU_ASSERT(ty); LUAU_ASSERT(ty);
return checkForFamilyInhabitance(follow(*ty), annotation->location); return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
} }
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation)
@ -1725,7 +1726,7 @@ struct TypeChecker2
visit(*fn->returnAnnotation); visit(*fn->returnAnnotation);
// If the function type has a family annotation, we need to see if we can suggest an annotation // If the function type has a function annotation, we need to see if we can suggest an annotation
if (normalizedFnTy) if (normalizedFnTy)
{ {
const FunctionType* inferredFtv = get<FunctionType>(normalizedFnTy->functions.parts.front()); const FunctionType* inferredFtv = get<FunctionType>(normalizedFnTy->functions.parts.front());
@ -1855,7 +1856,7 @@ struct TypeChecker2
if (get<TypeFunctionInstanceType>(expectedResult)) if (get<TypeFunctionInstanceType>(expectedResult))
{ {
checkForInternalFamily(expectedResult, expr->location); checkForInternalTypeFunction(expectedResult, expr->location);
return expectedResult; return expectedResult;
} }
@ -2254,7 +2255,7 @@ struct TypeChecker2
{ {
TypeId* resolvedTy = module->astResolvedTypes.find(ty); TypeId* resolvedTy = module->astResolvedTypes.find(ty);
if (resolvedTy) if (resolvedTy)
checkForFamilyInhabitance(follow(*resolvedTy), ty->location); checkForTypeFunctionInhabitance(follow(*resolvedTy), ty->location);
if (auto t = ty->as<AstTypeReference>()) if (auto t = ty->as<AstTypeReference>())
return visit(t); return visit(t);
@ -3057,6 +3058,8 @@ struct TypeChecker2
void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger, void check(NotNull<BuiltinTypes> builtinTypes, NotNull<UnifierSharedState> unifierState, NotNull<TypeCheckLimits> limits, DcrLogger* logger,
const SourceModule& sourceModule, Module* module) const SourceModule& sourceModule, Module* module)
{ {
LUAU_TIMETRACE_SCOPE("check", "Typechecking");
TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module}; TypeChecker2 typeChecker{builtinTypes, unifierState, limits, logger, &sourceModule, module};
typeChecker.visit(sourceModule.root); typeChecker.visit(sourceModule.root);

View File

@ -104,7 +104,7 @@ struct TypeFunctionReducer
VecDeque<TypeId> queuedTys; VecDeque<TypeId> queuedTys;
VecDeque<TypePackId> queuedTps; VecDeque<TypePackId> queuedTps;
TypeOrTypePackIdSet shouldGuess; TypeOrTypePackIdSet shouldGuess;
std::vector<TypeId> cyclicTypeFamilies; std::vector<TypeId> cyclicTypeFunctions;
TypeOrTypePackIdSet irreducible{nullptr}; TypeOrTypePackIdSet irreducible{nullptr};
FunctionGraphReductionResult result; FunctionGraphReductionResult result;
bool force = false; bool force = false;
@ -118,7 +118,7 @@ struct TypeFunctionReducer
, queuedTys(std::move(queuedTys)) , queuedTys(std::move(queuedTys))
, queuedTps(std::move(queuedTps)) , queuedTps(std::move(queuedTps))
, shouldGuess(std::move(shouldGuess)) , shouldGuess(std::move(shouldGuess))
, cyclicTypeFamilies(std::move(cyclicTypes)) , cyclicTypeFunctions(std::move(cyclicTypes))
, force(force) , force(force)
, location(location) , location(location)
{ {
@ -138,7 +138,7 @@ struct TypeFunctionReducer
if (is<TypeFunctionInstanceType>(ty)) if (is<TypeFunctionInstanceType>(ty))
{ {
for (auto t : cyclicTypeFamilies) for (auto t : cyclicTypeFunctions)
{ {
if (ty == t) if (ty == t)
return SkipTestResult::CyclicTypeFunction; return SkipTestResult::CyclicTypeFunction;
@ -339,7 +339,7 @@ struct TypeFunctionReducer
if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction) if (!testParameters(subject, tfit) && testCyclic != SkipTestResult::CyclicTypeFunction)
{ {
if (FFlag::DebugLuauLogTypeFamilies) if (FFlag::DebugLuauLogTypeFamilies)
printf("Irreducible due to irreducible/pending and a non-cyclic family\n"); printf("Irreducible due to irreducible/pending and a non-cyclic function\n");
return; return;
} }
@ -347,7 +347,7 @@ struct TypeFunctionReducer
if (tryGuessing(subject)) if (tryGuessing(subject))
return; return;
TypeFunctionReductionResult<TypeId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); TypeFunctionReductionResult<TypeId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleTypeFunctionReduction(subject, result); handleTypeFunctionReduction(subject, result);
} }
} }
@ -371,7 +371,7 @@ struct TypeFunctionReducer
if (tryGuessing(subject)) if (tryGuessing(subject))
return; return;
TypeFunctionReductionResult<TypePackId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); TypeFunctionReductionResult<TypePackId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleTypeFunctionReduction(subject, result); handleTypeFunctionReduction(subject, result);
} }
} }
@ -385,7 +385,7 @@ struct TypeFunctionReducer
} }
}; };
static FunctionGraphReductionResult reduceFamiliesInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, static FunctionGraphReductionResult reduceFunctionsInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess,
std::vector<TypeId> cyclics, Location location, TypeFunctionContext ctx, bool force) std::vector<TypeId> cyclics, Location location, TypeFunctionContext ctx, bool force)
{ {
TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; TypeFunctionReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
@ -422,7 +422,7 @@ FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location loc
if (collector.tys.empty() && collector.tps.empty()) if (collector.tys.empty() && collector.tps.empty())
return {}; return {};
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), return reduceFunctionsInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
std::move(collector.cyclicInstance), location, ctx, force); std::move(collector.cyclicInstance), location, ctx, force);
} }
@ -442,7 +442,7 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location
if (collector.tys.empty() && collector.tps.empty()) if (collector.tys.empty() && collector.tps.empty())
return {}; return {};
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), return reduceFunctionsInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
std::move(collector.cyclicInstance), location, ctx, force); std::move(collector.cyclicInstance), location, ctx, force);
} }
@ -528,7 +528,7 @@ static std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunct
return std::nullopt; return std::nullopt;
} }
TypeFunctionReductionResult<TypeId> notFamilyFn( TypeFunctionReductionResult<TypeId> notTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -545,14 +545,14 @@ TypeFunctionReductionResult<TypeId> notFamilyFn(
if (isPending(ty, ctx->solver)) if (isPending(ty, ctx->solver))
return {std::nullopt, false, {ty}, {}}; return {std::nullopt, false, {ty}, {}};
if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
return *result; return *result;
// `not` operates on anything and returns a `boolean` always. // `not` operates on anything and returns a `boolean` always.
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> lenFamilyFn( TypeFunctionReductionResult<TypeId> lenTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -604,7 +604,7 @@ TypeFunctionReductionResult<TypeId> lenFamilyFn(
if (normTy->hasTopTable() || get<TableType>(normalizedOperand)) if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
return *result; return *result;
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -644,7 +644,7 @@ TypeFunctionReductionResult<TypeId> lenFamilyFn(
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> unmFamilyFn( TypeFunctionReductionResult<TypeId> unmTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -689,7 +689,7 @@ TypeFunctionReductionResult<TypeId> unmFamilyFn(
if (normTy->isExactlyNumber()) if (normTy->isExactlyNumber())
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(notFamilyFn, instance, typeParams, packParams, ctx)) if (auto result = tryDistributeTypeFunctionApp(notTypeFunction, instance, typeParams, packParams, ctx))
return *result; return *result;
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -744,7 +744,7 @@ NotNull<Constraint> TypeFunctionContext::pushConstraint(ConstraintV&& c)
return newConstraint; return newConstraint;
} }
TypeFunctionReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams, TypeFunctionReductionResult<TypeId> numericBinopTypeFunction(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, const std::string metamethod) const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, const std::string metamethod)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -787,7 +787,7 @@ TypeFunctionReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const
rhsTy = *rhsMaybeGeneralized; rhsTy = *rhsMaybeGeneralized;
} }
// TODO: Normalization needs to remove cyclic type families from a `NormalizedType`. // TODO: Normalization needs to remove cyclic type functions from a `NormalizedType`.
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy); std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy); std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
@ -803,7 +803,7 @@ TypeFunctionReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber()) if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(numericBinopFamilyFn, instance, typeParams, packParams, ctx, metamethod)) if (auto result = tryDistributeTypeFunctionApp(numericBinopTypeFunction, instance, typeParams, packParams, ctx, metamethod))
return *result; return *result;
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -847,7 +847,7 @@ TypeFunctionReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const
return {extracted.head.front(), false, {}, {}}; return {extracted.head.front(), false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> addFamilyFn( TypeFunctionReductionResult<TypeId> addTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -856,10 +856,10 @@ TypeFunctionReductionResult<TypeId> addFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__add");
} }
TypeFunctionReductionResult<TypeId> subFamilyFn( TypeFunctionReductionResult<TypeId> subTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -868,10 +868,10 @@ TypeFunctionReductionResult<TypeId> subFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__sub");
} }
TypeFunctionReductionResult<TypeId> mulFamilyFn( TypeFunctionReductionResult<TypeId> mulTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -880,10 +880,10 @@ TypeFunctionReductionResult<TypeId> mulFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mul");
} }
TypeFunctionReductionResult<TypeId> divFamilyFn( TypeFunctionReductionResult<TypeId> divTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -892,10 +892,10 @@ TypeFunctionReductionResult<TypeId> divFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__div");
} }
TypeFunctionReductionResult<TypeId> idivFamilyFn( TypeFunctionReductionResult<TypeId> idivTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -904,10 +904,10 @@ TypeFunctionReductionResult<TypeId> idivFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__idiv");
} }
TypeFunctionReductionResult<TypeId> powFamilyFn( TypeFunctionReductionResult<TypeId> powTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -916,10 +916,10 @@ TypeFunctionReductionResult<TypeId> powFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__pow");
} }
TypeFunctionReductionResult<TypeId> modFamilyFn( TypeFunctionReductionResult<TypeId> modTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -928,10 +928,10 @@ TypeFunctionReductionResult<TypeId> modFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod"); return numericBinopTypeFunction(instance, typeParams, packParams, ctx, "__mod");
} }
TypeFunctionReductionResult<TypeId> concatFamilyFn( TypeFunctionReductionResult<TypeId> concatTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -987,7 +987,7 @@ TypeFunctionReductionResult<TypeId> concatFamilyFn(
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber())) if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
return {ctx->builtins->stringType, false, {}, {}}; return {ctx->builtins->stringType, false, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(concatFamilyFn, instance, typeParams, packParams, ctx)) if (auto result = tryDistributeTypeFunctionApp(concatTypeFunction, instance, typeParams, packParams, ctx))
return *result; return *result;
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -1039,7 +1039,7 @@ TypeFunctionReductionResult<TypeId> concatFamilyFn(
return {ctx->builtins->stringType, false, {}, {}}; return {ctx->builtins->stringType, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> andFamilyFn( TypeFunctionReductionResult<TypeId> andTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1090,7 +1090,7 @@ TypeFunctionReductionResult<TypeId> andFamilyFn(
return {overallResult.result, false, std::move(blockedTypes), {}}; return {overallResult.result, false, std::move(blockedTypes), {}};
} }
TypeFunctionReductionResult<TypeId> orFamilyFn( TypeFunctionReductionResult<TypeId> orTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1141,7 +1141,7 @@ TypeFunctionReductionResult<TypeId> orFamilyFn(
return {overallResult.result, false, std::move(blockedTypes), {}}; return {overallResult.result, false, std::move(blockedTypes), {}};
} }
static TypeFunctionReductionResult<TypeId> comparisonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams, static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(TypeId instance, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, const std::string metamethod) const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, const std::string metamethod)
{ {
@ -1162,7 +1162,7 @@ static TypeFunctionReductionResult<TypeId> comparisonFamilyFn(TypeId instance, c
else if (isPending(rhsTy, ctx->solver)) else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, false, {rhsTy}, {}}; return {std::nullopt, false, {rhsTy}, {}};
// Algebra Reduction Rules for comparison family functions // Algebra Reduction Rules for comparison type functions
// Note that comparing to never tells you nothing about the other operand // Note that comparing to never tells you nothing about the other operand
// lt< 'a , never> -> continue // lt< 'a , never> -> continue
// lt< never, 'a> -> continue // lt< never, 'a> -> continue
@ -1173,7 +1173,7 @@ static TypeFunctionReductionResult<TypeId> comparisonFamilyFn(TypeId instance, c
bool rhsFree = get<FreeType>(rhsTy) != nullptr; bool rhsFree = get<FreeType>(rhsTy) != nullptr;
if (canSubmitConstraint) if (canSubmitConstraint)
{ {
// Implement injective type families for comparison type families // Implement injective type functions for comparison type functions
// lt <number, t> implies t is number // lt <number, t> implies t is number
// lt <t, number> implies t is number // lt <t, number> implies t is number
if (lhsFree && isNumber(rhsTy)) if (lhsFree && isNumber(rhsTy))
@ -1238,7 +1238,7 @@ static TypeFunctionReductionResult<TypeId> comparisonFamilyFn(TypeId instance, c
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber()) if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
if (auto result = tryDistributeTypeFunctionApp(comparisonFamilyFn, instance, typeParams, packParams, ctx, metamethod)) if (auto result = tryDistributeTypeFunctionApp(comparisonTypeFunction, instance, typeParams, packParams, ctx, metamethod))
return *result; return *result;
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -1280,7 +1280,7 @@ static TypeFunctionReductionResult<TypeId> comparisonFamilyFn(TypeId instance, c
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> ltFamilyFn( TypeFunctionReductionResult<TypeId> ltTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1289,10 +1289,10 @@ TypeFunctionReductionResult<TypeId> ltFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt"); return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__lt");
} }
TypeFunctionReductionResult<TypeId> leFamilyFn( TypeFunctionReductionResult<TypeId> leTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1301,10 +1301,10 @@ TypeFunctionReductionResult<TypeId> leFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le"); return comparisonTypeFunction(instance, typeParams, packParams, ctx, "__le");
} }
TypeFunctionReductionResult<TypeId> eqFamilyFn( TypeFunctionReductionResult<TypeId> eqTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1381,7 +1381,7 @@ TypeFunctionReductionResult<TypeId> eqFamilyFn(
return {ctx->builtins->falseType, false, {}, {}}; return {ctx->builtins->falseType, false, {}, {}};
} }
return {std::nullopt, true, {}, {}}; // if it's not, then this family is irreducible! return {std::nullopt, true, {}, {}}; // if it's not, then this type function is irreducible!
} }
mmType = follow(*mmType); mmType = follow(*mmType);
@ -1435,7 +1435,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
}; };
TypeFunctionReductionResult<TypeId> refineFamilyFn( TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -1520,7 +1520,7 @@ TypeFunctionReductionResult<TypeId> refineFamilyFn(
return {resultTy, false, {}, {}}; return {resultTy, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> singletonFamilyFn( TypeFunctionReductionResult<TypeId> singletonTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -1557,7 +1557,7 @@ TypeFunctionReductionResult<TypeId> singletonFamilyFn(
return {ctx->builtins->unknownType, false, {}, {}}; return {ctx->builtins->unknownType, false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> unionFamilyFn( TypeFunctionReductionResult<TypeId> unionTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (!packParams.empty()) if (!packParams.empty())
@ -1618,7 +1618,7 @@ TypeFunctionReductionResult<TypeId> unionFamilyFn(
} }
TypeFunctionReductionResult<TypeId> intersectFamilyFn( TypeFunctionReductionResult<TypeId> intersectTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (!packParams.empty()) if (!packParams.empty())
@ -1725,7 +1725,7 @@ bool computeKeysOf(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& se
return false; return false;
} }
TypeFunctionReductionResult<TypeId> keyofFamilyImpl( TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, bool isRaw) const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, bool isRaw)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -1842,7 +1842,7 @@ TypeFunctionReductionResult<TypeId> keyofFamilyImpl(
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}}; return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> keyofFamilyFn( TypeFunctionReductionResult<TypeId> keyofTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -1851,10 +1851,10 @@ TypeFunctionReductionResult<TypeId> keyofFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
} }
TypeFunctionReductionResult<TypeId> rawkeyofFamilyFn( TypeFunctionReductionResult<TypeId> rawkeyofTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
@ -1863,7 +1863,7 @@ TypeFunctionReductionResult<TypeId> rawkeyofFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ true); return keyofFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
} }
/* Searches through table's or class's props/indexer to find the property of `ty` /* Searches through table's or class's props/indexer to find the property of `ty`
@ -1960,7 +1960,7 @@ bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result,
/* Vocabulary note: indexee refers to the type that contains the properties, /* Vocabulary note: indexee refers to the type that contains the properties,
indexer refers to the type that is used to access indexee indexer refers to the type that is used to access indexee
Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */ Example: index<Person, "name"> => `Person` is the indexee and `"name"` is the indexer */
TypeFunctionReductionResult<TypeId> indexFamilyImpl( TypeFunctionReductionResult<TypeId> indexFunctionImpl(
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, bool isRaw) const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx, bool isRaw)
{ {
TypeId indexeeTy = follow(typeParams.at(0)); TypeId indexeeTy = follow(typeParams.at(0));
@ -2064,7 +2064,7 @@ TypeFunctionReductionResult<TypeId> indexFamilyImpl(
return {ctx->arena->addType(UnionType{std::vector<TypeId>(properties.begin(), properties.end())}), false, {}, {}}; return {ctx->arena->addType(UnionType{std::vector<TypeId>(properties.begin(), properties.end())}), false, {}, {}};
} }
TypeFunctionReductionResult<TypeId> indexFamilyFn( TypeFunctionReductionResult<TypeId> indexTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -2073,10 +2073,10 @@ TypeFunctionReductionResult<TypeId> indexFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return indexFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ false);
} }
TypeFunctionReductionResult<TypeId> rawgetFamilyFn( TypeFunctionReductionResult<TypeId> rawgetTypeFunction(
TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx) TypeId instance, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFunctionContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
@ -2085,34 +2085,34 @@ TypeFunctionReductionResult<TypeId> rawgetFamilyFn(
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
return indexFamilyImpl(typeParams, packParams, ctx, /* isRaw */ true); return indexFunctionImpl(typeParams, packParams, ctx, /* isRaw */ true);
} }
BuiltinTypeFunctions::BuiltinTypeFunctions() BuiltinTypeFunctions::BuiltinTypeFunctions()
: notFunc{"not", notFamilyFn} : notFunc{"not", notTypeFunction}
, lenFunc{"len", lenFamilyFn} , lenFunc{"len", lenTypeFunction}
, unmFunc{"unm", unmFamilyFn} , unmFunc{"unm", unmTypeFunction}
, addFunc{"add", addFamilyFn} , addFunc{"add", addTypeFunction}
, subFunc{"sub", subFamilyFn} , subFunc{"sub", subTypeFunction}
, mulFunc{"mul", mulFamilyFn} , mulFunc{"mul", mulTypeFunction}
, divFunc{"div", divFamilyFn} , divFunc{"div", divTypeFunction}
, idivFunc{"idiv", idivFamilyFn} , idivFunc{"idiv", idivTypeFunction}
, powFunc{"pow", powFamilyFn} , powFunc{"pow", powTypeFunction}
, modFunc{"mod", modFamilyFn} , modFunc{"mod", modTypeFunction}
, concatFunc{"concat", concatFamilyFn} , concatFunc{"concat", concatTypeFunction}
, andFunc{"and", andFamilyFn} , andFunc{"and", andTypeFunction}
, orFunc{"or", orFamilyFn} , orFunc{"or", orTypeFunction}
, ltFunc{"lt", ltFamilyFn} , ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leFamilyFn} , leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqFamilyFn} , eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineFamilyFn} , refineFunc{"refine", refineTypeFunction}
, singletonFunc{"singleton", singletonFamilyFn} , singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionFamilyFn} , unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectFamilyFn} , intersectFunc{"intersect", intersectTypeFunction}
, keyofFunc{"keyof", keyofFamilyFn} , keyofFunc{"keyof", keyofTypeFunction}
, rawkeyofFunc{"rawkeyof", rawkeyofFamilyFn} , rawkeyofFunc{"rawkeyof", rawkeyofTypeFunction}
, indexFunc{"index", indexFamilyFn} , indexFunc{"index", indexTypeFunction}
, rawgetFunc{"rawget", rawgetFamilyFn} , rawgetFunc{"rawget", rawgetTypeFunction}
{ {
} }

View File

@ -83,7 +83,7 @@ bool TypeFunctionReductionGuesser::isFunctionGenericsSaturated(const FunctionTyp
void TypeFunctionReductionGuesser::dumpGuesses() void TypeFunctionReductionGuesser::dumpGuesses()
{ {
for (auto [tf, t] : familyReducesTo) for (auto [tf, t] : functionReducesTo)
printf("Type family %s ~~> %s\n", toString(tf).c_str(), toString(t).c_str()); printf("Type family %s ~~> %s\n", toString(tf).c_str(), toString(t).c_str());
for (auto [t, t_] : substitutable) for (auto [t, t_] : substitutable)
printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str()); printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str());
@ -175,7 +175,7 @@ TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunction
toInfer.clear(); toInfer.clear();
cyclicInstances.clear(); cyclicInstances.clear();
familyReducesTo.clear(); functionReducesTo.clear();
substitutable.clear(); substitutable.clear();
return TypeFunctionReductionGuessResult{results, recommendedAnnotation}; return TypeFunctionReductionGuessResult{results, recommendedAnnotation};
@ -196,44 +196,44 @@ std::optional<TypeId> TypeFunctionReductionGuesser::guessType(TypeId arg)
} }
if (get<TypeFunctionInstanceType>(t)) if (get<TypeFunctionInstanceType>(t))
{ {
if (familyReducesTo.contains(t)) if (functionReducesTo.contains(t))
return familyReducesTo[t]; return functionReducesTo[t];
} }
return {}; return {};
} }
bool TypeFunctionReductionGuesser::isNumericBinopFamily(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isNumericBinopFunction(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "add" || instance.family->name == "sub" || instance.family->name == "mul" || instance.family->name == "div" || return instance.function->name == "add" || instance.function->name == "sub" || instance.function->name == "mul" || instance.function->name == "div" ||
instance.family->name == "idiv" || instance.family->name == "pow" || instance.family->name == "mod"; instance.function->name == "idiv" || instance.function->name == "pow" || instance.function->name == "mod";
} }
bool TypeFunctionReductionGuesser::isComparisonFamily(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isComparisonFunction(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "lt" || instance.family->name == "le" || instance.family->name == "eq"; return instance.function->name == "lt" || instance.function->name == "le" || instance.function->name == "eq";
} }
bool TypeFunctionReductionGuesser::isOrAndFamily(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isOrAndFunction(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "or" || instance.family->name == "and"; return instance.function->name == "or" || instance.function->name == "and";
} }
bool TypeFunctionReductionGuesser::isNotFamily(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isNotFunction(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "not"; return instance.function->name == "not";
} }
bool TypeFunctionReductionGuesser::isLenFamily(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isLenFunction(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "len"; return instance.function->name == "len";
} }
bool TypeFunctionReductionGuesser::isUnaryMinus(const TypeFunctionInstanceType& instance) bool TypeFunctionReductionGuesser::isUnaryMinus(const TypeFunctionInstanceType& instance)
{ {
return instance.family->name == "unm"; return instance.function->name == "unm";
} }
// Operand is assignable if it looks like a cyclic family instance, or a generic type // Operand is assignable if it looks like a cyclic function instance, or a generic type
bool TypeFunctionReductionGuesser::operandIsAssignable(TypeId ty) bool TypeFunctionReductionGuesser::operandIsAssignable(TypeId ty)
{ {
if (get<TypeFunctionInstanceType>(ty)) if (get<TypeFunctionInstanceType>(ty))
@ -257,8 +257,8 @@ std::optional<TypeId> TypeFunctionReductionGuesser::tryAssignOperandType(TypeId
// We try to check if we guessed a type for it // We try to check if we guessed a type for it
if (auto tfit = get<TypeFunctionInstanceType>(ty)) if (auto tfit = get<TypeFunctionInstanceType>(ty))
{ {
if (familyReducesTo.contains(ty)) if (functionReducesTo.contains(ty))
return {familyReducesTo[ty]}; return {functionReducesTo[ty]};
} }
// If ty is a generic, we need to check if we inferred a substitution // If ty is a generic, we need to check if we inferred a substitution
@ -298,24 +298,24 @@ void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, con
TypeFunctionInferenceResult result; TypeFunctionInferenceResult result;
LUAU_ASSERT(instance); LUAU_ASSERT(instance);
// TODO: Make an inexhaustive version of this warn in the compiler? // TODO: Make an inexhaustive version of this warn in the compiler?
if (isNumericBinopFamily(*instance)) if (isNumericBinopFunction(*instance))
result = inferNumericBinopFamily(instance); result = inferNumericBinopFunction(instance);
else if (isComparisonFamily(*instance)) else if (isComparisonFunction(*instance))
result = inferComparisonFamily(instance); result = inferComparisonFunction(instance);
else if (isOrAndFamily(*instance)) else if (isOrAndFunction(*instance))
result = inferOrAndFamily(instance); result = inferOrAndFunction(instance);
else if (isNotFamily(*instance)) else if (isNotFunction(*instance))
result = inferNotFamily(instance); result = inferNotFunction(instance);
else if (isLenFamily(*instance)) else if (isLenFunction(*instance))
result = inferLenFamily(instance); result = inferLenFunction(instance);
else if (isUnaryMinus(*instance)) else if (isUnaryMinus(*instance))
result = inferUnaryMinusFamily(instance); result = inferUnaryMinusFunction(instance);
else else
result = {{}, builtins->unknownType}; result = {{}, builtins->unknownType};
TypeId resultInference = follow(result.familyResultInference); TypeId resultInference = follow(result.functionResultInference);
if (!familyReducesTo.contains(resultInference)) if (!functionReducesTo.contains(resultInference))
familyReducesTo[ty] = resultInference; functionReducesTo[ty] = resultInference;
for (size_t i = 0; i < instance->typeArguments.size(); i++) for (size_t i = 0; i < instance->typeArguments.size(); i++)
{ {
@ -325,8 +325,8 @@ void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, con
TypeId inference = follow(result.operandInference[i]); TypeId inference = follow(result.operandInference[i]);
if (auto tfit = get<TypeFunctionInstanceType>(arg)) if (auto tfit = get<TypeFunctionInstanceType>(arg))
{ {
if (!familyReducesTo.contains(arg)) if (!functionReducesTo.contains(arg))
familyReducesTo.try_insert(arg, inference); functionReducesTo.try_insert(arg, inference);
} }
else if (auto gt = get<GenericType>(arg)) else if (auto gt = get<GenericType>(arg))
substitutable[arg] = inference; substitutable[arg] = inference;
@ -334,17 +334,17 @@ void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, con
} }
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNumericBinopFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNumericBinopFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 2); LUAU_ASSERT(instance->typeArguments.size() == 2);
TypeFunctionInferenceResult defaultNumericBinopInference{{builtins->numberType, builtins->numberType}, builtins->numberType}; TypeFunctionInferenceResult defaultNumericBinopInference{{builtins->numberType, builtins->numberType}, builtins->numberType};
return defaultNumericBinopInference; return defaultNumericBinopInference;
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 2); LUAU_ASSERT(instance->typeArguments.size() == 2);
// Comparison families are lt/le/eq. // Comparison functions are lt/le/eq.
// Heuristic: these are type functions from t -> t -> bool // Heuristic: these are type functions from t -> t -> bool
TypeId lhsTy = follow(instance->typeArguments[0]); TypeId lhsTy = follow(instance->typeArguments[0]);
@ -365,7 +365,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferComparisonFamily(
return comparisonInference(builtins->numberType); return comparisonInference(builtins->numberType);
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 2); LUAU_ASSERT(instance->typeArguments.size() == 2);
@ -384,7 +384,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFamily(const
bool lhsTruthy = lty ? lty->isTruthy() : false; bool lhsTruthy = lty ? lty->isTruthy() : false;
bool rhsTruthy = rty ? rty->isTruthy() : false; bool rhsTruthy = rty ? rty->isTruthy() : false;
// If at the end, we still don't have good substitutions, return the default type // If at the end, we still don't have good substitutions, return the default type
if (instance->family->name == "or") if (instance->function->name == "or")
{ {
if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy)) if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy))
return defaultAndOrInference; return defaultAndOrInference;
@ -398,7 +398,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFamily(const
return {{builtins->unknownType, rhsTy}, rhsTy}; return {{builtins->unknownType, rhsTy}, rhsTy};
} }
if (instance->family->name == "and") if (instance->function->name == "and")
{ {
if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy)) if (operandIsAssignable(lhsTy) && operandIsAssignable(rhsTy))
@ -416,7 +416,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferOrAndFamily(const
return defaultAndOrInference; return defaultAndOrInference;
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNotFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNotFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 1); LUAU_ASSERT(instance->typeArguments.size() == 1);
TypeId opTy = follow(instance->typeArguments[0]); TypeId opTy = follow(instance->typeArguments[0]);
@ -425,7 +425,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferNotFamily(const T
return {{opTy}, builtins->booleanType}; return {{opTy}, builtins->booleanType};
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferLenFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferLenFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 1); LUAU_ASSERT(instance->typeArguments.size() == 1);
TypeId opTy = follow(instance->typeArguments[0]); TypeId opTy = follow(instance->typeArguments[0]);
@ -434,7 +434,7 @@ TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferLenFamily(const T
return {{opTy}, builtins->numberType}; return {{opTy}, builtins->numberType};
} }
TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferUnaryMinusFamily(const TypeFunctionInstanceType* instance) TypeFunctionInferenceResult TypeFunctionReductionGuesser::inferUnaryMinusFunction(const TypeFunctionInstanceType* instance)
{ {
LUAU_ASSERT(instance->typeArguments.size() == 1); LUAU_ASSERT(instance->typeArguments.size() == 1);
TypeId opTy = follow(instance->typeArguments[0]); TypeId opTy = follow(instance->typeArguments[0]);

View File

@ -92,19 +92,19 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
, ice(ice) , ice(ice)
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
, recursionLimit(FInt::LuauTypeInferRecursionLimit) , recursionLimit(FInt::LuauTypeInferRecursionLimit)
, uninhabitedTypeFamilies(nullptr) , uninhabitedTypeFunctions(nullptr)
{ {
} }
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice, Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice,
DenseHashSet<const void*>* uninhabitedTypeFamilies) DenseHashSet<const void*>* uninhabitedTypeFunctions)
: arena(arena) : arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, scope(scope) , scope(scope)
, ice(ice) , ice(ice)
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
, recursionLimit(FInt::LuauTypeInferRecursionLimit) , recursionLimit(FInt::LuauTypeInferRecursionLimit)
, uninhabitedTypeFamilies(uninhabitedTypeFamilies) , uninhabitedTypeFunctions(uninhabitedTypeFunctions)
{ {
} }
@ -135,7 +135,7 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
// - *blocked* <: unknown // - *blocked* <: unknown
if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy)) if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy))
{ {
if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTy) || uninhabitedTypeFamilies->contains(superTy))) if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTy) || uninhabitedTypeFunctions->contains(superTy)))
return true; return true;
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
@ -539,7 +539,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
if (isIrresolvable(subTp) || isIrresolvable(superTp)) if (isIrresolvable(subTp) || isIrresolvable(superTp))
{ {
if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTp) || uninhabitedTypeFamilies->contains(superTp))) if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTp) || uninhabitedTypeFunctions->contains(superTp)))
return true; return true;
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});

View File

@ -11,7 +11,6 @@ Id UnionFind::makeSet()
Id id{parents.size()}; Id id{parents.size()};
parents.push_back(id); parents.push_back(id);
ranks.push_back(0); ranks.push_back(0);
return id; return id;
} }

View File

@ -36,7 +36,7 @@ local x: Account = 5
CHECK_EQ("Type 'number' could not be converted into 'Account'", toString(result.errors[0])); CHECK_EQ("Type 'number' could not be converted into 'Account'", toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_family_errors") TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_function_errors")
{ {
frontend.options.retainFullTypeGraphs = false; frontend.options.retainFullTypeGraphs = false;
@ -54,7 +54,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "binary_op_type_family_errors")
CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0])); CHECK_EQ("Type 'string' could not be converted into 'number'", toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_family_errors") TEST_CASE_FIXTURE(BuiltinsFixture, "unary_op_type_function_errors")
{ {
frontend.options.retainFullTypeGraphs = false; frontend.options.retainFullTypeGraphs = false;

View File

@ -311,7 +311,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked")
REQUIRE(bool(cExports)); REQUIRE(bool(cExports));
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("{ a: any, b: any }", toString(*cExports)); CHECK_EQ("{ a: { hello: any }, b: { hello: any } }", toString(*cExports));
else else
CHECK_EQ("{| a: any, b: any |}", toString(*cExports)); CHECK_EQ("{| a: any, b: any |}", toString(*cExports));
} }

View File

@ -28,7 +28,7 @@ end
REQUIRE(0 == result.warnings.size()); REQUIRE(0 == result.warnings.size());
} }
TEST_CASE_FIXTURE(Fixture, "type_family_fully_reduces") TEST_CASE_FIXTURE(Fixture, "type_function_fully_reduces")
{ {
LintResult result = lint(R"( LintResult result = lint(R"(
function fib(n) function fib(n)

View File

@ -404,8 +404,16 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression")
CHECK(!isSubtype(any, str)); CHECK(!isSubtype(any, str));
CHECK(isSubtype(str, any)); CHECK(isSubtype(str, any));
// We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
// inhabitants). any = err | unknown, so under semantic subtyping, {} U unknown = unknown
if (FFlag::DebugLuauDeferredConstraintResolution)
{
CHECK(isSubtype(any, unk));
}
else
{
CHECK(!isSubtype(any, unk)); CHECK(!isSubtype(any, unk));
CHECK(isSubtype(unk, any)); }
CHECK(!isSubtype(err, str)); CHECK(!isSubtype(err, str));
CHECK(!isSubtype(str, err)); CHECK(!isSubtype(str, err));

View File

@ -470,9 +470,11 @@ TEST_CASE_FIXTURE(SubtypeFixture, "variadic_subpath_in_pack")
CHECK(!result.isSubtype); CHECK(!result.isSubtype);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "any <!: unknown") TEST_CASE_FIXTURE(SubtypeFixture, "any <: unknown")
{ {
CHECK_IS_NOT_SUBTYPE(builtinTypes->anyType, builtinTypes->unknownType); // We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
// inhabitants). any = err | unknown, so under semantic subtyping, {} U unknown = unknown
CHECK_IS_SUBTYPE(builtinTypes->anyType, builtinTypes->unknownType);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown") TEST_CASE_FIXTURE(SubtypeFixture, "number? <: unknown")
@ -990,11 +992,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "semantic_subtyping_disj")
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 where t2 = {trim: (t2) -> string}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t1 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t2 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
@ -1003,11 +1009,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <: t2 wh
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 where t2 = {trim: (t2) -> t2}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t1 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t2 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {ty}); tt->props["trim"] = fn({ty}, {ty});
}); });
@ -1016,11 +1026,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> string} <!: t2 w
TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}") TEST_CASE_FIXTURE(SubtypeFixture, "t1 where t1 = {trim: (t1) -> t1} <!: t2 where t2 = {trim: (t2) -> string}")
{ {
TypeId t1 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t1 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {ty}); tt->props["trim"] = fn({ty}, {ty});
}); });
TypeId t2 = cyclicTable([&](TypeId ty, TableType* tt) { TypeId t2 = cyclicTable(
[&](TypeId ty, TableType* tt)
{
tt->props["trim"] = fn({ty}, {builtinTypes->stringType}); tt->props["trim"] = fn({ty}, {builtinTypes->stringType});
}); });
@ -1286,7 +1300,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }
CHECK_IS_SUBTYPE(tableToPropType, otherType); CHECK_IS_SUBTYPE(tableToPropType, otherType);
} }
TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_family_instance") TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance")
{ {
TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType, TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType,
builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}}); builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}});

View File

@ -15,14 +15,14 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
struct FamilyFixture : Fixture struct TypeFunctionFixture : Fixture
{ {
TypeFunction swapFamily; TypeFunction swapFunction;
FamilyFixture() TypeFunctionFixture()
: Fixture(true, false) : Fixture(true, false)
{ {
swapFamily = TypeFunction{/* name */ "Swap", swapFunction = TypeFunction{/* name */ "Swap",
/* reducer */ /* reducer */
[](TypeId instance, const std::vector<TypeId>& tys, const std::vector<TypePackId>& tps, [](TypeId instance, const std::vector<TypeId>& tys, const std::vector<TypePackId>& tps,
NotNull<TypeFunctionContext> ctx) -> TypeFunctionReductionResult<TypeId> { NotNull<TypeFunctionContext> ctx) -> TypeFunctionReductionResult<TypeId> {
@ -54,14 +54,14 @@ struct FamilyFixture : Fixture
ScopePtr globalScope = frontend.globals.globalScope; ScopePtr globalScope = frontend.globals.globalScope;
globalScope->exportedTypeBindings["Swap"] = globalScope->exportedTypeBindings["Swap"] =
TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFunctionInstanceType{NotNull{&swapFamily}, {t}, {}})}; TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFunctionInstanceType{NotNull{&swapFunction}, {t}, {}})};
freeze(frontend.globals.globalTypes); freeze(frontend.globals.globalTypes);
} }
}; };
TEST_SUITE_BEGIN("TypeFunctionTests"); TEST_SUITE_BEGIN("TypeFunctionTests");
TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family") TEST_CASE_FIXTURE(TypeFunctionFixture, "basic_type_function")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -83,7 +83,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family")
CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0])); CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
}; };
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret") TEST_CASE_FIXTURE(TypeFunctionFixture, "function_as_fn_ret")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -102,7 +102,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret")
CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0])); CHECK("Type function instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg") TEST_CASE_FIXTURE(TypeFunctionFixture, "function_as_fn_arg")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -121,7 +121,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg")
CHECK("Type function instance Swap<a> is uninhabited" == toString(result.errors[1])); CHECK("Type function instance Swap<a> is uninhabited" == toString(result.errors[1]));
} }
TEST_CASE_FIXTURE(FamilyFixture, "resolve_deep_families") TEST_CASE_FIXTURE(TypeFunctionFixture, "resolve_deep_functions")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -134,7 +134,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "resolve_deep_families")
CHECK("number" == toString(requireType("x"))); CHECK("number" == toString(requireType("x")));
} }
TEST_CASE_FIXTURE(FamilyFixture, "unsolvable_family") TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -152,7 +152,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "unsolvable_family")
} }
} }
TEST_CASE_FIXTURE(FamilyFixture, "table_internal_families") TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -172,7 +172,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "table_internal_families")
CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean | boolean | boolean> is uninhabited"); CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean | boolean | boolean> is uninhabited");
} }
TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families") TEST_CASE_FIXTURE(TypeFunctionFixture, "function_internal_functions")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -193,7 +193,7 @@ TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families")
CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean> is uninhabited"); CHECK(toString(result.errors[0]) == "Type function instance Swap<boolean> is uninhabited");
} }
TEST_CASE_FIXTURE(Fixture, "add_family_at_work") TEST_CASE_FIXTURE(Fixture, "add_function_at_work")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -216,7 +216,7 @@ TEST_CASE_FIXTURE(Fixture, "add_family_at_work")
CHECK(toString(result.errors[1]) == "Type function instance Add<string, number> is uninhabited"); CHECK(toString(result.errors[1]) == "Type function instance Add<string, number> is uninhabited");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_family_at_work") TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_function_at_work")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -229,7 +229,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_add_family_at_work")
CHECK(toString(requireTypeAlias("T")) == "number"); CHECK(toString(requireTypeAlias("T")) == "number");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "mul_family_with_union_of_multiplicatives") TEST_CASE_FIXTURE(BuiltinsFixture, "mul_function_with_union_of_multiplicatives")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -252,7 +252,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mul_family_with_union_of_multiplicatives")
CHECK(toString(requireTypeAlias("T")) == "Vec2 | Vec3"); CHECK(toString(requireTypeAlias("T")) == "Vec2 | Vec3");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "mul_family_with_union_of_multiplicatives_2") TEST_CASE_FIXTURE(BuiltinsFixture, "mul_function_with_union_of_multiplicatives_2")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -272,7 +272,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "mul_family_with_union_of_multiplicatives_2")
CHECK(toString(requireTypeAlias("T")) == "Vec3"); CHECK(toString(requireTypeAlias("T")) == "Vec3");
} }
TEST_CASE_FIXTURE(Fixture, "internal_families_raise_errors") TEST_CASE_FIXTURE(Fixture, "internal_functions_raise_errors")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -288,7 +288,7 @@ TEST_CASE_FIXTURE(Fixture, "internal_families_raise_errors")
"signature; this construct cannot be type-checked at this time"); "signature; this construct cannot be type-checked at this time");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_can_be_shadowed") TEST_CASE_FIXTURE(BuiltinsFixture, "type_functions_can_be_shadowed")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -313,7 +313,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_can_be_shadowed")
CHECK(toString(requireType("plus")) == "<a, b>(a, b) -> add<a, b>"); CHECK(toString(requireType("plus")) == "<a, b>(a, b) -> add<a, b>");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_inhabited_with_normalization") TEST_CASE_FIXTURE(BuiltinsFixture, "type_functions_inhabited_with_normalization")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -331,7 +331,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_families_inhabited_with_normalization")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -352,7 +352,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works")
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works_with_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_works_with_metatables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -375,7 +375,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_works_with_metatables")
CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"w\" | \"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_part") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_errors_if_it_has_nontable_part")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -393,7 +393,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_errors_if_it_has_nontable_
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid"); CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'keyof<MyObject | boolean>' is invalid");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_string_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -421,7 +421,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_string_indexer")
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_common_subset_if_union_of_differing_tables") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_common_subset_if_union_of_differing_tables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -442,7 +442,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_common_subset_if_union_of_
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_never_for_empty_table") TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_function_never_for_empty_table")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -457,7 +457,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_type_family_never_for_empty_table")
CHECK(toString(requireType("foo")) == "never"); CHECK(toString(requireType("foo")) == "never");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_works") TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_works")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -478,7 +478,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_works")
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_ignores_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_ignores_metatables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -501,7 +501,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_ignores_metatables")
CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"x\" | \"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontable_part") TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_errors_if_it_has_nontable_part")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -519,7 +519,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_errors_if_it_has_nontab
CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid"); CHECK(toString(result.errors[1]) == "Type 'MyObject | boolean' does not have keys, so 'rawkeyof<MyObject | boolean>' is invalid");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_of_differing_tables") TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_common_subset_if_union_of_differing_tables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -540,7 +540,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_common_subset_if_union_
CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp)); CHECK_EQ("\"y\" | \"z\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_never_for_empty_table") TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_never_for_empty_table")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -555,7 +555,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_family_never_for_empty_table")
CHECK(toString(requireType("foo")) == "never"); CHECK(toString(requireType("foo")) == "never");
} }
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_works_on_classes") TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_on_classes")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -575,7 +575,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_works_on_classes")
CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tpm->givenTp)); CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_part") TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_errors_if_it_has_nonclass_part")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -592,7 +592,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_errors_if_it_has_nonclass_par
CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid"); CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof<BaseClass | boolean>' is invalid");
} }
TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_differing_classes") TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_common_subset_if_union_of_differing_classes")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -606,7 +606,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_dif
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(ClassFixture, "binary_type_family_works_with_default_argument") TEST_CASE_FIXTURE(ClassFixture, "binary_type_function_works_with_default_argument")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -699,7 +699,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
CHECK(get<FunctionExitsWithoutReturning>(result.errors[0])); CHECK(get<FunctionExitsWithoutReturning>(result.errors[0]));
} }
TEST_CASE_FIXTURE(FamilyFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType") TEST_CASE_FIXTURE(TypeFunctionFixture, "fuzzer_numeric_binop_doesnt_assert_on_generalizeFreeType")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
Module 'l0': Module 'l0':
@ -714,7 +714,7 @@ _(setmetatable(_,{[...]=_,}))
)"); )");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_concat_family_at_work") TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_concat_function_at_work")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -827,7 +827,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_equivalence_with_distributivity")
CHECK(toString(requireTypeAlias("U")) == "A | A | B | B"); CHECK(toString(requireTypeAlias("U")) == "A | A | B | B");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "we_shouldnt_warn_that_a_reducible_type_family_is_uninhabited") TEST_CASE_FIXTURE(BuiltinsFixture, "we_shouldnt_warn_that_a_reducible_type_function_is_uninhabited")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -857,7 +857,7 @@ end
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -880,7 +880,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works")
CHECK_EQ("string", toString(tpm->givenTp)); CHECK_EQ("string", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_array") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -895,7 +895,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_array")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_generic_types") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_generic_types")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -917,7 +917,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_generic_types")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_errors_w_bad_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_bad_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -933,7 +933,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_errors_w_bad_indexer")
CHECK(toString(result.errors[1]) == "Property 'boolean' does not exist on type 'MyObject'"); CHECK(toString(result.errors[1]) == "Property 'boolean' does not exist on type 'MyObject'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_errors_w_var_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_errors_w_var_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -950,7 +950,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_errors_w_var_indexer")
CHECK(toString(result.errors[1]) == "Unknown type 'key'"); CHECK(toString(result.errors[1]) == "Unknown type 'key'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_union_type_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_union_type_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -968,7 +968,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_union_type_indexer
CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'"); CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_union_type_indexee") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_union_type_indexee")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -987,7 +987,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_union_type_indexee
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'"); CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_rfc_alternative_section") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_rfc_alternative_section")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1005,7 +1005,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_rfc_alternative_section")
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject'"); CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject'");
} }
TEST_CASE_FIXTURE(ClassFixture, "index_type_family_works_on_classes") TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1019,7 +1019,7 @@ TEST_CASE_FIXTURE(ClassFixture, "index_type_family_works_on_classes")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_index_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_index_metatables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1045,7 +1045,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_family_works_w_index_metatables")
CHECK(toString(result.errors[0]) == "Property '\"Car\"' does not exist on type 'exampleClass2'"); CHECK(toString(result.errors[0]) == "Property '\"Car\"' does not exist on type 'exampleClass2'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1067,7 +1067,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works")
CHECK_EQ("string", toString(tpm->givenTp)); CHECK_EQ("string", toString(tpm->givenTp));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_array") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_array")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1081,7 +1081,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_array")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_errors_w_var_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_errors_w_var_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1097,7 +1097,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_errors_w_var_indexer")
CHECK(toString(result.errors[1]) == "Unknown type 'key'"); CHECK(toString(result.errors[1]) == "Unknown type 'key'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_union_type_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexer")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1113,7 +1113,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_union_type_indexe
CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'"); CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_union_type_indexee") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexee")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1130,7 +1130,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_union_type_indexe
CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'"); CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_index_metatables") TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_index_metatables")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1150,7 +1150,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_family_works_w_index_metatables"
CHECK(toString(result.errors[1]) == "Property '\"Bar\" | \"Foo\"' does not exist on type 'exampleClass3'"); CHECK(toString(result.errors[1]) == "Property '\"Bar\" | \"Foo\"' does not exist on type 'exampleClass3'");
} }
TEST_CASE_FIXTURE(ClassFixture, "rawget_type_family_errors_w_classes") TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;

View File

@ -32,6 +32,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any?" == toString(requireType("a")));
else
CHECK(builtinTypes->anyType == requireType("a")); CHECK(builtinTypes->anyType == requireType("a"));
} }
@ -50,6 +53,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any2")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any?" == toString(requireType("a")));
else
CHECK("any" == toString(requireType("a"))); CHECK("any" == toString(requireType("a")));
} }
@ -66,6 +72,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any?" == toString(requireType("a")));
else
CHECK("any" == toString(requireType("a"))); CHECK("any" == toString(requireType("a")));
} }
@ -80,6 +89,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any2")
end end
)"); )");
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any?" == toString(requireType("a")));
else
CHECK("any" == toString(requireType("a"))); CHECK("any" == toString(requireType("a")));
} }
@ -96,6 +108,9 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any_pack")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("any?" == toString(requireType("a")));
else
CHECK("any" == toString(requireType("a"))); CHECK("any" == toString(requireType("a")));
} }
@ -291,6 +306,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_comp
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("any?", toString(requireType("b")));
else
CHECK_EQ("any", toString(requireType("b"))); CHECK_EQ("any", toString(requireType("b")));
} }

View File

@ -1424,7 +1424,7 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
REQUIRE(get<UnknownSymbol>(result.errors[1])); REQUIRE(get<UnknownSymbol>(result.errors[1]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_families_work_in_subtyping") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_functions_work_in_subtyping")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;

View File

@ -745,7 +745,7 @@ TEST_CASE_FIXTURE(Fixture, "strict_binary_op_where_lhs_unknown")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
LUAU_REQUIRE_ERROR_COUNT(ops.size(), result); LUAU_REQUIRE_ERROR_COUNT(ops.size(), result);
CHECK_EQ("Type family instance Add<a, b> depends on generic function parameters but does not appear in the function signature; this " CHECK_EQ("Type function instance Add<a, b> depends on generic function parameters but does not appear in the function signature; this "
"construct cannot be type-checked at this time", "construct cannot be type-checked at this time",
toString(result.errors[0])); toString(result.errors[0]));
CHECK_EQ("Unknown type used in - operation; consider adding a type annotation to 'a'", toString(result.errors[1])); CHECK_EQ("Unknown type used in - operation; consider adding a type annotation to 'a'", toString(result.errors[1]));
@ -1456,7 +1456,7 @@ return startsWith
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "add_type_family_works") TEST_CASE_FIXTURE(Fixture, "add_type_function_works")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -1473,7 +1473,7 @@ TEST_CASE_FIXTURE(Fixture, "add_type_family_works")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK(toString(requireType("a")) == "number"); CHECK(toString(requireType("a")) == "number");
CHECK(toString(requireType("b")) == "Add<string, string>"); CHECK(toString(requireType("b")) == "Add<string, string>");
CHECK(toString(result.errors[0]) == "Type family instance Add<string, string> is uninhabited"); CHECK(toString(result.errors[0]) == "Type function instance Add<string, string> is uninhabited");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "normalize_strings_comparison") TEST_CASE_FIXTURE(BuiltinsFixture, "normalize_strings_comparison")

View File

@ -25,7 +25,7 @@ LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_shouldnt_seal_table_in_len_family_fn") TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_shouldnt_seal_table_in_len_function_fn")
{ {
if (!FFlag::DebugLuauDeferredConstraintResolution) if (!FFlag::DebugLuauDeferredConstraintResolution)
return; return;
@ -4579,4 +4579,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_table_assertion_crash")
)"); )");
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "table::insert_should_not_report_errors_when_correct_overload_is_picked")
{
CheckResult result = check(R"(
type cs = { GetTagged : (cs, string) -> any}
local destroyQueue: {any} = {} -- pair of (time, coin)
local tick : () -> any
local CS : cs
local DESTROY_DELAY
local function SpawnCoin()
local spawns = CS:GetTagged('CoinSpawner')
local n : any
local StartPos = spawns[n].CFrame
local Coin = script.Coin:Clone()
Coin.CFrame = StartPos
Coin.Parent = workspace.Coins
table.insert(destroyQueue, {tick() + DESTROY_DELAY, Coin})
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -39,7 +39,6 @@ Differ.negation
FrontendTest.environments FrontendTest.environments
FrontendTest.imported_table_modification_2 FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
FrontendTest.nocheck_cycle_used_by_checked
FrontendTest.trace_requires_in_nonstrict_mode FrontendTest.trace_requires_in_nonstrict_mode
GenericsTests.apply_type_function_nested_generics1 GenericsTests.apply_type_function_nested_generics1
GenericsTests.better_mismatch_error_messages GenericsTests.better_mismatch_error_messages
@ -54,7 +53,7 @@ GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many GenericsTests.generic_argument_count_too_many
GenericsTests.generic_factories GenericsTests.generic_factories
GenericsTests.generic_functions_in_types GenericsTests.generic_functions_in_types
GenericsTests.generic_type_families_work_in_subtyping GenericsTests.generic_type_functions_work_in_subtyping
GenericsTests.generic_type_pack_parentheses GenericsTests.generic_type_pack_parentheses
GenericsTests.generic_type_pack_unification1 GenericsTests.generic_type_pack_unification1
GenericsTests.generic_type_pack_unification2 GenericsTests.generic_type_pack_unification2
@ -246,18 +245,18 @@ TypeAliases.report_shadowed_aliases
TypeAliases.type_alias_local_mutation TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeFunctionTests.add_family_at_work TypeFunctionTests.add_function_at_work
TypeFunctionTests.cyclic_add_family_at_work TypeFunctionTests.cyclic_add_function_at_work
TypeFunctionTests.cyclic_concat_family_at_work TypeFunctionTests.cyclic_concat_function_at_work
TypeFunctionTests.didnt_quite_exceed_distributivity_limits TypeFunctionTests.didnt_quite_exceed_distributivity_limits
TypeFunctionTests.ensure_equivalence_with_distributivity TypeFunctionTests.ensure_equivalence_with_distributivity
TypeFunctionTests.family_as_fn_arg TypeFunctionTests.function_as_fn_arg
TypeFunctionTests.index_type_family_works_w_generic_types TypeFunctionTests.index_type_function_works_w_generic_types
TypeFunctionTests.internal_families_raise_errors TypeFunctionTests.internal_functions_raise_errors
TypeFunctionTests.keyof_oss_crash_gh1161 TypeFunctionTests.keyof_oss_crash_gh1161
TypeFunctionTests.mul_family_with_union_of_multiplicatives TypeFunctionTests.mul_function_with_union_of_multiplicatives
TypeFunctionTests.mul_family_with_union_of_multiplicatives_2 TypeFunctionTests.mul_function_with_union_of_multiplicatives_2
TypeFunctionTests.unsolvable_family TypeFunctionTests.unsolvable_function
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
TypeInfer.check_type_infer_recursion_count TypeInfer.check_type_infer_recursion_count
TypeInfer.checking_should_not_ice TypeInfer.checking_should_not_ice
@ -277,12 +276,6 @@ TypeInfer.tc_if_else_expressions_expected_type_3
TypeInfer.type_infer_recursion_limit_no_ice TypeInfer.type_infer_recursion_limit_no_ice
TypeInfer.type_infer_recursion_limit_normalizer TypeInfer.type_infer_recursion_limit_normalizer
TypeInfer.unify_nearly_identical_recursive_types TypeInfer.unify_nearly_identical_recursive_types
TypeInferAnyError.for_in_loop_iterator_is_any
TypeInferAnyError.for_in_loop_iterator_is_any2
TypeInferAnyError.for_in_loop_iterator_is_any_pack
TypeInferAnyError.for_in_loop_iterator_returns_any
TypeInferAnyError.for_in_loop_iterator_returns_any2
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferClasses.callable_classes TypeInferClasses.callable_classes
TypeInferClasses.cannot_unify_class_instance_with_primitive TypeInferClasses.cannot_unify_class_instance_with_primitive
TypeInferClasses.class_type_mismatch_with_name_conflict TypeInferClasses.class_type_mismatch_with_name_conflict
@ -370,7 +363,7 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.promise_type_error_too_complex TypeInferOOP.promise_type_error_too_complex
TypeInferOperators.add_type_family_works TypeInferOperators.add_type_function_works
TypeInferOperators.cli_38355_recursive_union TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compound_assign_result_must_be_compatible_with_var TypeInferOperators.compound_assign_result_must_be_compatible_with_var
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs TypeInferOperators.concat_op_on_free_lhs_and_string_rhs