Sync to upstream/release/622 (#1232)

# What's changed?

* Improved the actual message for the type errors for `cannot call
non-function` when attempting to call a union of functions/callable
tables. The error now correctly explains the issue is an inability to
determine the return type of the call in this situation.
* Resolve an issue where tables and metatables were not correctly being
cloned during instantiation (fixes #1176).
* Refactor `luaM_getnextgcopage` to `luaM_getnextpage` (generally
removing `gco` prefix where appropriate).
* Optimize `table.move` between tables for large ranges of elements.
* Reformat a bunch of code automatically using `clang-format`.

### New Type Solver

* Clean up minimally-used or unused constraints in the constraint solver
(`InstantiationConstraint`, `SetOpConstraint`,
`SingletonOrTopTypeConstraint`).
* Add a builtin `singleton` type family to replace
`SingletonOrTopTypeConstraint` when inferring refinements.
* Fixed a crash involving type path reasoning by recording when type
family reduction has taken place in the path.
* Improved constraint ordering by blocking on unreduced types families
that are not yet proven uninhabitable.
* Improved the handling of `SetIndexerConstraints` for both better
inference quality and to resolve crashes.
* Fix a crash when normalizing cyclic unions of intersections.
* Fix a crash when normalizing an intersection with the negation of
`unknown`.
* Fix a number of crashes caused by missing `follow` calls on `TypeId`s.
* Changed type family reduction to correctly use a semantic notion of
uninhabited types, rather than checking for `never` types specifically.
* Refactor the `union` and `intersect` type families to be variadic.

### Native Code Generation

* Improve translation for userdata key get/set and userdata/vector
namecall.
* Provide `[top level]` and `[anonymous]` as function names to
`FunctionStats` as appropriate when no function name is available.
* Disable unwind support on Android platforms since it is unsupported.
*  

---

### Internal Contributors

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: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Vighnesh <vvijay@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>
This commit is contained in:
aaron 2024-04-19 14:48:02 -07:00 committed by GitHub
parent 9c2146288d
commit 68bd1b2349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1403 additions and 752 deletions

View File

@ -52,13 +52,6 @@ struct GeneralizationConstraint
std::vector<TypeId> interiorTypes; std::vector<TypeId> interiorTypes;
}; };
// subType ~ inst superType
struct InstantiationConstraint
{
TypeId subType;
TypeId superType;
};
// variables ~ iterate iterator // variables ~ iterate iterator
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables. // Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
struct IterableConstraint struct IterableConstraint
@ -229,17 +222,6 @@ struct SetIndexerConstraint
TypeId propType; TypeId propType;
}; };
// if negation:
// result ~ if isSingleton D then ~D else unknown where D = discriminantType
// if not negation:
// result ~ if isSingleton D then D else unknown where D = discriminantType
struct SingletonOrTopTypeConstraint
{
TypeId resultType;
TypeId discriminantType;
bool negated;
};
// resultType ~ unpack sourceTypePack // resultType ~ unpack sourceTypePack
// //
// Similar to PackSubtypeConstraint, but with one important difference: If the // Similar to PackSubtypeConstraint, but with one important difference: If the
@ -269,22 +251,6 @@ struct Unpack1Constraint
bool resultIsLValue = false; bool resultIsLValue = false;
}; };
// resultType ~ T0 op T1 op ... op TN
//
// op is either union or intersection. If any of the input types are blocked,
// this constraint will block unless forced.
struct SetOpConstraint
{
enum
{
Intersection,
Union
} mode;
TypeId resultType;
std::vector<TypeId> types;
};
// ty ~ reduce ty // ty ~ reduce ty
// //
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing. // Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
@ -301,10 +267,9 @@ struct ReducePackConstraint
TypePackId tp; TypePackId tp;
}; };
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint, using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, IterableConstraint, NameConstraint,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint, HasIndexerConstraint, SetIndexerConstraint, UnpackConstraint, Unpack1Constraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
struct Constraint struct Constraint
{ {

View File

@ -91,6 +91,9 @@ 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.
DenseHashSet<const void*> uninhabitedTypeFamilies{{}};
// Recorded errors that take place within the solver. // Recorded errors that take place within the solver.
ErrorVec errors; ErrorVec errors;
@ -124,7 +127,6 @@ struct ConstraintSolver
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
@ -134,22 +136,18 @@ struct ConstraintSolver
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen); bool tryDispatchHasIndexer(
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen);
bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint);
/// (dispatched, found) where std::pair<bool, std::optional<TypeId>> tryDispatchSetIndexer(
/// - dispatched: this constraint can be considered having dispatched. NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
/// - found: true if adding an indexer for a particular type was allowed.
std::pair<bool, bool> tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue); bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);

View File

@ -69,7 +69,8 @@ private:
struct SolveResult struct SolveResult
{ {
enum OverloadCallResult { enum OverloadCallResult
{
Ok, Ok,
CodeTooComplex, CodeTooComplex,
OccursCheckFailed, OccursCheckFailed,
@ -87,16 +88,8 @@ struct SolveResult
// Helper utility, presently used for binary operator type families. // Helper utility, presently used for binary operator type families.
// //
// 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( SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,
NotNull<TypeArena> arena, NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, const Location& location, TypeId fn,
NotNull<BuiltinTypes> builtinTypes, TypePackId argsPack);
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope,
const Location& location,
TypeId fn,
TypePackId argsPack
);
} // namespace Luau } // namespace Luau

View File

@ -208,7 +208,8 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm); SubtypingResult isCovariantWith(
SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);

View File

@ -17,4 +17,4 @@ class AstExpr;
TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes, TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Unifier2> unifier, TypeId expectedType, TypeId exprType, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Unifier2> unifier, TypeId expectedType, TypeId exprType,
const AstExpr* expr, std::vector<TypeId>& toBlock); const AstExpr* expr, std::vector<TypeId>& toBlock);
} } // namespace Luau

View File

@ -552,6 +552,27 @@ struct TypeFamilyInstanceType
std::vector<TypeId> typeArguments; std::vector<TypeId> typeArguments;
std::vector<TypePackId> packArguments; std::vector<TypePackId> packArguments;
TypeFamilyInstanceType(NotNull<const TypeFamily> family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: family(family)
, typeArguments(typeArguments)
, packArguments(packArguments)
{
}
TypeFamilyInstanceType(const TypeFamily& family, std::vector<TypeId> typeArguments)
: family{&family}
, typeArguments(typeArguments)
, packArguments{}
{
}
TypeFamilyInstanceType(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
: family{&family}
, typeArguments(typeArguments)
, packArguments(packArguments)
{
}
}; };
/** Represents a pending type alias instantiation. /** Represents a pending type alias instantiation.

View File

@ -48,6 +48,11 @@ struct TypeArena
{ {
return addTypePack(TypePackVar(std::move(tp))); return addTypePack(TypePackVar(std::move(tp)));
} }
TypeId addTypeFamily(const TypeFamily& family, std::initializer_list<TypeId> types);
TypeId addTypeFamily(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
TypePackId addTypePackFamily(const TypePackFamily& family, std::initializer_list<TypeId> types);
TypePackId addTypePackFamily(const TypePackFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
}; };
void freeze(TypeArena& arena); void freeze(TypeArena& arena);

View File

@ -99,8 +99,8 @@ struct TypeFamilyReductionResult
}; };
template<typename T> template<typename T>
using ReducerFunction = using ReducerFunction = std::function<TypeFamilyReductionResult<T>(
std::function<TypeFamilyReductionResult<T>(T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>; T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
/// Represents a type function that may be applied to map a series of types and /// Represents a type function that may be applied to map a series of types and
/// type packs to a single output type. /// type packs to a single output type.
@ -189,6 +189,7 @@ struct BuiltinTypeFamilies
TypeFamily eqFamily; TypeFamily eqFamily;
TypeFamily refineFamily; TypeFamily refineFamily;
TypeFamily singletonFamily;
TypeFamily unionFamily; TypeFamily unionFamily;
TypeFamily intersectFamily; TypeFamily intersectFamily;

View File

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

View File

@ -79,9 +79,18 @@ enum class PackField
Tail, Tail,
}; };
/// Component that represents the result of a reduction
/// `resultType` is `never` if the reduction could not proceed
struct Reduction
{
TypeId resultType;
bool operator==(const Reduction& other) const;
};
/// A single component of a path, representing one inner type or type pack to /// A single component of a path, representing one inner type or type pack to
/// traverse into. /// traverse into.
using Component = Luau::Variant<Property, Index, TypeField, PackField>; using Component = Luau::Variant<Property, Index, TypeField, PackField, Reduction>;
/// A path through a type or type pack accessing a particular type or type pack /// A path through a type or type pack accessing a particular type or type pack
/// contained within. /// contained within.
@ -156,6 +165,7 @@ struct PathHash
size_t operator()(const Index& idx) const; size_t operator()(const Index& idx) const;
size_t operator()(const TypeField& field) const; size_t operator()(const TypeField& field) const;
size_t operator()(const PackField& field) const; size_t operator()(const PackField& field) const;
size_t operator()(const Reduction& reduction) const;
size_t operator()(const Component& component) const; size_t operator()(const Component& component) const;
size_t operator()(const Path& path) const; size_t operator()(const Path& path) const;
}; };

View File

@ -48,8 +48,12 @@ struct Unifier2
int recursionLimit = 0; int recursionLimit = 0;
std::vector<ConstraintV> incompleteSubtypes; std::vector<ConstraintV> incompleteSubtypes;
// null if not in a constraint solving context
DenseHashSet<const void*>* uninhabitedTypeFamilies;
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);
/** 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

@ -24,7 +24,6 @@
*/ */
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauSetMetatableOnUnionsOfTables, false);
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false); LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
namespace Luau namespace Luau
@ -1067,7 +1066,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target)) else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
{ {
} }
else if (FFlag::LuauSetMetatableOnUnionsOfTables && isTableUnion(target)) else if (isTableUnion(target))
{ {
const UnionType* ut = get<UnionType>(target); const UnionType* ut = get<UnionType>(target);
LUAU_ASSERT(ut); LUAU_ASSERT(ut);

View File

@ -233,7 +233,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
TypeId result = arena->addType(BlockedType{}); TypeId result = arena->addType(BlockedType{});
NotNull<Constraint> genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())}); NotNull<Constraint> genConstraint =
addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())});
getMutable<BlockedType>(result)->setOwner(genConstraint); getMutable<BlockedType>(result)->setOwner(genConstraint);
forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) { forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
genConstraint->dependencies.push_back(NotNull{c.get()}); genConstraint->dependencies.push_back(NotNull{c.get()});
@ -407,13 +408,13 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca
else if (auto proposition = get<Proposition>(refinement)) else if (auto proposition = get<Proposition>(refinement))
{ {
TypeId discriminantTy = proposition->discriminantTy; TypeId discriminantTy = proposition->discriminantTy;
if (!sense && !eq)
discriminantTy = arena->addType(NegationType{proposition->discriminantTy}); // if we have a negative sense, then we need to negate the discriminant
else if (eq) if (!sense)
{ discriminantTy = arena->addType(NegationType{discriminantTy});
discriminantTy = arena->addType(BlockedType{});
constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy, !sense}); if (eq)
} discriminantTy = arena->addTypeFamily(kBuiltinTypeFamilies.singletonFamily, {discriminantTy});
for (const RefinementKey* key = proposition->key; key; key = key->parent) for (const RefinementKey* key = proposition->key; key; key = key->parent)
{ {
@ -525,11 +526,13 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
{ {
if (mustDeferIntersection(ty) || mustDeferIntersection(dt)) if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily}, NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt}, {ty, dt},
{}, {},
}, scope, location); },
scope, location);
ty = resultType; ty = resultType;
} }
@ -961,7 +964,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
// With or without self // With or without self
TypeId generalizedType = arena->addType(BlockedType{}); TypeId generalizedType = arena->addType(BlockedType{});
Checkpoint start = checkpoint(this); Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
bool sigFullyDefined = !hasFreeType(sig.signature); bool sigFullyDefined = !hasFreeType(sig.signature);
@ -1056,7 +1058,16 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
} }
}); });
if (auto blocked = getMutable<BlockedType>(generalizedType))
// We need to check if the blocked type has no owner here because
// if a function is defined twice anywhere in the program like:
// `function f() end` and then later like `function f() end`
// Then there will be exactly one definition in the scope for it because it's a global
// (this is the same as writing f = function() end)
// Therefore, when we visit() the multiple different expression of this global variable
// They will all be aliased to the same blocked type, which means we can create multiple constraints
// for the same blocked type.
if (auto blocked = getMutable<BlockedType>(generalizedType); blocked && !blocked->getOwner())
blocked->setOwner(addConstraint(scope, std::move(c))); blocked->setOwner(addConstraint(scope, std::move(c)));
} }
@ -1708,8 +1719,8 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
* 4. Solve the call * 4. Solve the call
*/ */
NotNull<Constraint> checkConstraint = NotNull<Constraint> checkConstraint = addConstraint(scope, call->func->location,
addConstraint(scope, call->func->location, FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}}); FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}});
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) { forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) {
checkConstraint->dependencies.emplace_back(constraint.get()); checkConstraint->dependencies.emplace_back(constraint.get());
@ -1901,7 +1912,8 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
scope->rvalueRefinements[key->def] = result; scope->rvalueRefinements[key->def] = result;
} }
auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)}); auto c =
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)});
getMutable<BlockedType>(result)->setOwner(c); getMutable<BlockedType>(result)->setOwner(c);
if (key) if (key)
@ -1957,7 +1969,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint endCheckpoint = checkpoint(this); Checkpoint endCheckpoint = checkpoint(this);
TypeId generalizedTy = arena->addType(BlockedType{}); TypeId generalizedTy = arena->addType(BlockedType{});
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())}); NotNull<Constraint> gc =
addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
interiorTypes.pop_back(); interiorTypes.pop_back();
@ -1992,29 +2005,35 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
{ {
case AstExprUnary::Op::Not: case AstExprUnary::Op::Not:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.notFamily}, NotNull{&kBuiltinTypeFamilies.notFamily},
{operandType}, {operandType},
{}, {},
}, scope, unary->location); },
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)}; return Inference{resultType, refinementArena.negation(refinement)};
} }
case AstExprUnary::Op::Len: case AstExprUnary::Op::Len:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.lenFamily}, NotNull{&kBuiltinTypeFamilies.lenFamily},
{operandType}, {operandType},
{}, {},
}, scope, unary->location); },
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)}; return Inference{resultType, refinementArena.negation(refinement)};
} }
case AstExprUnary::Op::Minus: case AstExprUnary::Op::Minus:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unmFamily}, NotNull{&kBuiltinTypeFamilies.unmFamily},
{operandType}, {operandType},
{}, {},
}, scope, unary->location); },
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)}; return Inference{resultType, refinementArena.negation(refinement)};
} }
default: // msvc can't prove that this is exhaustive. default: // msvc can't prove that this is exhaustive.
@ -2030,138 +2049,168 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar
{ {
case AstExprBinary::Op::Add: case AstExprBinary::Op::Add:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.addFamily}, NotNull{&kBuiltinTypeFamilies.addFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Sub: case AstExprBinary::Op::Sub:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.subFamily}, NotNull{&kBuiltinTypeFamilies.subFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Mul: case AstExprBinary::Op::Mul:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.mulFamily}, NotNull{&kBuiltinTypeFamilies.mulFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Div: case AstExprBinary::Op::Div:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.divFamily}, NotNull{&kBuiltinTypeFamilies.divFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::FloorDiv: case AstExprBinary::Op::FloorDiv:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.idivFamily}, NotNull{&kBuiltinTypeFamilies.idivFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Pow: case AstExprBinary::Op::Pow:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.powFamily}, NotNull{&kBuiltinTypeFamilies.powFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Mod: case AstExprBinary::Op::Mod:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.modFamily}, NotNull{&kBuiltinTypeFamilies.modFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Concat: case AstExprBinary::Op::Concat:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.concatFamily}, NotNull{&kBuiltinTypeFamilies.concatFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::And: case AstExprBinary::Op::And:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.andFamily}, NotNull{&kBuiltinTypeFamilies.andFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Or: case AstExprBinary::Op::Or:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.orFamily}, NotNull{&kBuiltinTypeFamilies.orFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareLt: case AstExprBinary::Op::CompareLt:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily}, NotNull{&kBuiltinTypeFamilies.ltFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily}, NotNull{&kBuiltinTypeFamilies.ltFamily},
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily}, NotNull{&kBuiltinTypeFamilies.leFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily}, NotNull{&kBuiltinTypeFamilies.leFamily},
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareEq: case AstExprBinary::Op::CompareEq:
case AstExprBinary::Op::CompareNe: case AstExprBinary::Op::CompareNe:
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.eqFamily}, NotNull{&kBuiltinTypeFamilies.eqFamily},
{leftType, rightType}, {leftType, rightType},
{}, {},
}, scope, binary->location); },
scope, binary->location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::Op__Count: case AstExprBinary::Op::Op__Count:
@ -2613,13 +2662,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
TypeId indexKey = indexKeyLowerBound.size() == 1 TypeId indexKey = indexKeyLowerBound.size() == 1
? *indexKeyLowerBound.begin() ? *indexKeyLowerBound.begin()
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}) : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
;
TypeId indexValue = indexValueLowerBound.size() == 1 TypeId indexValue = indexValueLowerBound.size() == 1
? *indexValueLowerBound.begin() ? *indexValueLowerBound.begin()
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}) : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
;
ttv->indexer = TableIndexer{indexKey, indexValue}; ttv->indexer = TableIndexer{indexKey, indexValue};
} }
@ -3236,22 +3283,26 @@ void ConstraintGenerator::reportCodeTooComplex(Location location)
TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily}, NotNull{&kBuiltinTypeFamilies.unionFamily},
{lhs, rhs}, {lhs, rhs},
{}, {},
}, scope, location); },
scope, location);
return resultType; return resultType;
} }
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{ {
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{ TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.intersectFamily}, NotNull{&kBuiltinTypeFamilies.intersectFamily},
{lhs, rhs}, {lhs, rhs},
{}, {},
}, scope, location); },
scope, location);
return resultType; return resultType;
} }
@ -3329,9 +3380,13 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
scope->bindings[symbol] = Binding{tys.front(), location}; scope->bindings[symbol] = Binding{tys.front(), location};
else else
{ {
TypeId ty = arena->addType(BlockedType{}); TypeId ty = createFamilyInstance(
auto c = addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)}); TypeFamilyInstanceType{
getMutable<BlockedType>(ty)->setOwner(c); NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(tys),
{},
},
globalScope, Location{});
scope->bindings[symbol] = Binding{ty, location}; scope->bindings[symbol] = Binding{ty, location};
} }

View File

@ -346,7 +346,8 @@ void ConstraintSolver::run()
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
{ {
printf("Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str()); printf(
"Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str());
dump(this, opts); dump(this, opts);
printf("Bindings:\n"); printf("Bindings:\n");
dumpBindings(rootScope, opts); dumpBindings(rootScope, opts);
@ -492,8 +493,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*psc, constraint, force); success = tryDispatch(*psc, constraint, force);
else if (auto gc = get<GeneralizationConstraint>(*constraint)) else if (auto gc = get<GeneralizationConstraint>(*constraint))
success = tryDispatch(*gc, constraint, force); success = tryDispatch(*gc, constraint, force);
else if (auto ic = get<InstantiationConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto ic = get<IterableConstraint>(*constraint)) else if (auto ic = get<IterableConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force); success = tryDispatch(*ic, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint)) else if (auto nc = get<NameConstraint>(*constraint))
@ -514,14 +513,10 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*spc, constraint); success = tryDispatch(*spc, constraint);
else if (auto spc = get<SetIndexerConstraint>(*constraint)) else if (auto spc = get<SetIndexerConstraint>(*constraint))
success = tryDispatch(*spc, constraint, force); success = tryDispatch(*spc, constraint, force);
else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint))
success = tryDispatch(*sottc, constraint);
else if (auto uc = get<UnpackConstraint>(*constraint)) else if (auto uc = get<UnpackConstraint>(*constraint))
success = tryDispatch(*uc, constraint); success = tryDispatch(*uc, constraint);
else if (auto uc = get<Unpack1Constraint>(*constraint)) else if (auto uc = get<Unpack1Constraint>(*constraint))
success = tryDispatch(*uc, constraint); success = tryDispatch(*uc, constraint);
else if (auto soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force);
else if (auto rc = get<ReduceConstraint>(*constraint)) else if (auto rc = get<ReduceConstraint>(*constraint))
success = tryDispatch(*rc, constraint, force); success = tryDispatch(*rc, constraint, force);
else if (auto rpc = get<ReducePackConstraint>(*constraint)) else if (auto rpc = get<ReducePackConstraint>(*constraint))
@ -611,40 +606,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
return true; return true;
} }
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force)
{
if (isBlocked(c.superType))
return block(c.superType, constraint);
if (!blockOnPendingTypes(c.superType, constraint))
return false;
// TODO childLimit
std::optional<TypeId> instantiated = instantiate(builtinTypes, NotNull{arena}, NotNull{&limits}, constraint->scope, c.superType);
LUAU_ASSERT(get<BlockedType>(c.subType));
LUAU_ASSERT(canMutate(c.subType, constraint));
if (!instantiated.has_value())
{
reportError(UnificationTooComplex{}, constraint->location);
bindBlockedType(c.subType, errorRecoveryType(), c.superType, constraint);
unblock(c.subType, constraint->location);
return true;
}
bindBlockedType(c.subType, *instantiated, c.superType, constraint);
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
queuer.traverse(c.subType);
unblock(c.subType, constraint->location);
return true;
}
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{ {
/* /*
@ -936,7 +897,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// Type function application will happily give us the exact same type if // Type function application will happily give us the exact same type if
// there are e.g. generic saturatedTypeArguments that go unused. // there are e.g. generic saturatedTypeArguments that go unused.
bool needsClone = follow(tf->type) == target; const TableType* tfTable = getTableType(tf->type);
bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target));
// Only tables have the properties we're trying to set. // Only tables have the properties we're trying to set.
TableType* ttv = getMutableTableType(target); TableType* ttv = getMutableTableType(target);
@ -1462,7 +1424,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
return true; return true;
} }
bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen) bool ConstraintSolver::tryDispatchHasIndexer(
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen)
{ {
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit}; RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit};
@ -1481,12 +1444,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType}; FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
asMutable(resultType)->ty.emplace<FreeType>(freeResult); asMutable(resultType)->ty.emplace<FreeType>(freeResult);
TypeId upperBound = arena->addType(TableType{ TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, TableState::Unsealed});
/* props */ {},
TableIndexer{indexType, resultType},
TypeLevel{},
TableState::Unsealed
});
unify(constraint, subjectType, upperBound); unify(constraint, subjectType, upperBound);
@ -1625,7 +1583,7 @@ struct BlockedTypeFinder : TypeOnceVisitor
} }
}; };
} } // namespace
bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint) bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint)
{ {
@ -1651,25 +1609,24 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const
return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType, seen); return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType, seen);
} }
std::pair<bool, bool> ConstraintSolver::tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds) std::pair<bool, std::optional<TypeId>> ConstraintSolver::tryDispatchSetIndexer(
NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds)
{ {
if (isBlocked(subjectType)) if (isBlocked(subjectType))
return {block(subjectType, constraint), false}; return {block(subjectType, constraint), std::nullopt};
if (auto tt = getMutable<TableType>(subjectType)) if (auto tt = getMutable<TableType>(subjectType))
{ {
if (tt->indexer) if (tt->indexer)
{ {
unify(constraint, indexType, tt->indexer->indexType); unify(constraint, indexType, tt->indexer->indexType);
bindBlockedType(propType, tt->indexer->indexResultType, subjectType, constraint); return {true, tt->indexer->indexResultType};
return {true, true};
} }
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed) else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
{ {
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint); TypeId resultTy = freshType(arena, builtinTypes, constraint->scope.get());
tt->indexer = TableIndexer{indexType, propType}; tt->indexer = TableIndexer{indexType, resultTy};
return {true, true}; return {true, resultTy};
} }
} }
else if (auto ft = getMutable<FreeType>(subjectType); ft && expandFreeTypeBounds) else if (auto ft = getMutable<FreeType>(subjectType); ft && expandFreeTypeBounds)
@ -1678,41 +1635,51 @@ std::pair<bool, bool> ConstraintSolver::tryDispatchSetIndexer(NotNull<const Cons
// Therefore, we only care about the upper bound. // Therefore, we only care about the upper bound.
// //
// We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer. // We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer.
auto [dispatched, found] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/ false); auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/false);
if (dispatched && !found) if (dispatched && !resultTy)
{ {
// Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find. // Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find.
found = true; resultTy = freshType(arena, builtinTypes, constraint->scope.get());
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()}); TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
TableType* tt2 = getMutable<TableType>(tableTy); TableType* tt2 = getMutable<TableType>(tableTy);
tt2->indexer = TableIndexer{indexType, propType}; tt2->indexer = TableIndexer{indexType, *resultTy};
ft->upperBound = simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint. ft->upperBound =
simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint.
} }
return {dispatched, found}; return {dispatched, resultTy};
} }
else if (auto it = get<IntersectionType>(subjectType)) else if (auto it = get<IntersectionType>(subjectType))
{ {
std::pair<bool, bool> result{true, true}; bool dispatched = true;
std::vector<TypeId> results;
for (TypeId part : it) for (TypeId part : it)
{ {
auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds); auto [dispatched2, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
result.first &= dispatched; dispatched &= dispatched2;
result.second &= found; results.push_back(found.value_or(builtinTypes->errorRecoveryType()));
if (!dispatched)
return {dispatched, std::nullopt};
} }
return result; TypeId resultTy = arena->addType(TypeFamilyInstanceType{
} NotNull{&kBuiltinTypeFamilies.unionFamily},
else if (is<AnyType, ErrorType, NeverType>(subjectType) && expandFreeTypeBounds) std::move(results),
{ {},
bindBlockedType(propType, subjectType, subjectType, constraint); });
return {true, true};
}
return {true, false}; pushConstraint(constraint->scope, constraint->location, ReduceConstraint{resultTy});
return {dispatched, resultTy};
}
else if (is<AnyType, ErrorType, NeverType>(subjectType))
return {true, subjectType};
return {true, std::nullopt};
} }
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
@ -1721,54 +1688,38 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
if (isBlocked(subjectType)) if (isBlocked(subjectType))
return block(subjectType, constraint); return block(subjectType, constraint);
auto [dispatched, found] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/ true); auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/true);
if (dispatched) if (dispatched)
{ {
if (!found) bindBlockedType(c.propType, resultTy.value_or(builtinTypes->errorRecoveryType()), subjectType, constraint);
bindBlockedType(c.propType, builtinTypes->errorRecoveryType(), subjectType, constraint);
unblock(c.propType, constraint->location); unblock(c.propType, constraint->location);
} }
return dispatched; return dispatched;
} }
bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint)
{
if (isBlocked(c.discriminantType))
return false;
TypeId followed = follow(c.discriminantType);
// `nil` is a singleton type too! There's only one value of type `nil`.
if (c.negated && (get<SingletonType>(followed) || isNil(followed)))
*asMutable(c.resultType) = NegationType{c.discriminantType};
else if (!c.negated && get<SingletonType>(followed))
*asMutable(c.resultType) = BoundType{c.discriminantType};
else
*asMutable(c.resultType) = BoundType{builtinTypes->anyType};
unblock(c.resultType, constraint->location);
return true;
}
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue) bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue)
{ {
resultTy = follow(resultTy); resultTy = follow(resultTy);
LUAU_ASSERT(canMutate(resultTy, constraint)); LUAU_ASSERT(canMutate(resultTy, constraint));
if (auto lt = getMutable<LocalType>(resultTy); resultIsLValue && lt) auto tryExpand = [&](TypeId ty) {
{ LocalType* lt = getMutable<LocalType>(ty);
if (!lt || !resultIsLValue)
return;
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result; lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
LUAU_ASSERT(lt->blockCount > 0); LUAU_ASSERT(lt->blockCount > 0);
--lt->blockCount; --lt->blockCount;
LUAU_ASSERT(0 <= lt->blockCount);
if (0 == lt->blockCount) if (0 == lt->blockCount)
asMutable(resultTy)->ty.emplace<BoundType>(lt->domain); asMutable(ty)->ty.emplace<BoundType>(lt->domain);
} };
if (auto ut = get<UnionType>(resultTy))
std::for_each(begin(ut), end(ut), tryExpand);
else if (get<LocalType>(resultTy))
tryExpand(resultTy);
else if (get<BlockedType>(resultTy)) else if (get<BlockedType>(resultTy))
{ {
if (follow(srcTy) == resultTy) if (follow(srcTy) == resultTy)
@ -1823,21 +1774,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
TypeId srcTy = follow(srcPack.head[i]); TypeId srcTy = follow(srcPack.head[i]);
TypeId resultTy = follow(*resultIter); TypeId resultTy = follow(*resultIter);
if (resultTy)
{
// when we preserve the error-suppression of types through typestate,
// we introduce a union with the error type, so we need to find the local type in those options to update.
if (auto ut = getMutable<UnionType>(resultTy))
{
for (auto opt : ut->options)
tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue);
}
else
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue); tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
}
else
unify(constraint, srcTy, resultTy);
++resultIter; ++resultIter;
++i; ++i;
@ -1877,34 +1814,6 @@ bool ConstraintSolver::tryDispatch(const Unpack1Constraint& c, NotNull<const Con
return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue); return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue);
} }
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
{
bool blocked = false;
for (TypeId ty : c.types)
{
if (isBlocked(ty))
{
blocked = true;
block(ty, constraint);
}
}
if (blocked && !force)
return false;
LUAU_ASSERT(SetOpConstraint::Union == c.mode);
TypeId res = builtinTypes->neverType;
for (TypeId ty : c.types)
res = simplifyUnion(builtinTypes, arena, res, ty).result;
bindBlockedType(c.resultType, res, c.resultType, constraint);
asMutable(c.resultType)->ty.emplace<BoundType>(res);
return true;
}
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
{ {
TypeId ty = follow(c.ty); TypeId ty = follow(c.ty);
@ -1917,6 +1826,20 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks) for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location); unblock(r, constraint->location);
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
if (force || reductionFinished)
{
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock.
for (auto error : result.errors)
{
if (auto utf = get<UninhabitedTypeFamily>(error))
uninhabitedTypeFamilies.insert(utf->ty);
else if (auto utpf = get<UninhabitedTypePackFamily>(error))
uninhabitedTypeFamilies.insert(utpf->tp);
}
}
if (force) if (force)
return true; return true;
@ -1926,7 +1849,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId b : result.blockedPacks) for (TypePackId b : result.blockedPacks)
block(b, constraint); block(b, constraint);
return result.blockedTypes.empty() && result.blockedPacks.empty(); return reductionFinished;
} }
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
@ -1941,6 +1864,20 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
for (TypePackId r : result.reducedPacks) for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location); unblock(r, constraint->location);
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
if (force || reductionFinished)
{
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock.
for (auto error : result.errors)
{
if (auto utf = get<UninhabitedTypeFamily>(error))
uninhabitedTypeFamilies.insert(utf->ty);
else if (auto utpf = get<UninhabitedTypePackFamily>(error))
uninhabitedTypeFamilies.insert(utpf->tp);
}
}
if (force) if (force)
return true; return true;
@ -1950,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
for (TypePackId b : result.blockedPacks) for (TypePackId b : result.blockedPacks)
block(b, constraint); block(b, constraint);
return result.blockedTypes.empty() && result.blockedPacks.empty(); return reductionFinished;
} }
bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
@ -2405,7 +2342,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}}; Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFamilies};
const bool ok = u2.unify(subTy, superTy); const bool ok = u2.unify(subTy, superTy);
@ -2672,12 +2609,20 @@ bool ConstraintSolver::isBlocked(TypeId ty)
if (auto lt = get<LocalType>(ty)) if (auto lt = get<LocalType>(ty))
return lt->blockCount > 0; return lt->blockCount > 0;
if (auto tfit = get<TypeFamilyInstanceType>(ty))
return uninhabitedTypeFamilies.contains(ty) == false;
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty); return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
} }
bool ConstraintSolver::isBlocked(TypePackId tp) bool ConstraintSolver::isBlocked(TypePackId tp)
{ {
return nullptr != get<BlockedTypePack>(follow(tp)); tp = follow(tp);
if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
return uninhabitedTypeFamilies.contains(tp) == false;
return nullptr != get<BlockedTypePack>(tp);
} }
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint) bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)

View File

@ -15,6 +15,8 @@
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
static std::string wrongNumberOfArgsString( static std::string wrongNumberOfArgsString(
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false) size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
{ {
@ -335,8 +337,65 @@ struct ErrorConverter
return e.message; return e.message;
} }
std::optional<TypeId> findCallMetamethod(TypeId type) const
{
type = follow(type);
std::optional<TypeId> metatable;
if (const MetatableType* mtType = get<MetatableType>(type))
metatable = mtType->metatable;
else if (const ClassType* classType = get<ClassType>(type))
metatable = classType->metatable;
if (!metatable)
return std::nullopt;
TypeId unwrapped = follow(*metatable);
if (get<AnyType>(unwrapped))
return unwrapped;
const TableType* mtt = getTableType(unwrapped);
if (!mtt)
return std::nullopt;
auto it = mtt->props.find("__call");
if (it != mtt->props.end())
return it->second.type();
else
return std::nullopt;
}
std::string operator()(const Luau::CannotCallNonFunction& e) const std::string operator()(const Luau::CannotCallNonFunction& e) const
{ {
if (DFFlag::LuauImproveNonFunctionCallError)
{
if (auto unionTy = get<UnionType>(follow(e.ty)))
{
std::string err = "Cannot call a value of the union type:";
for (auto option : unionTy)
{
option = follow(option);
if (get<FunctionType>(option) || findCallMetamethod(option))
{
err += "\n | " + toString(option);
continue;
}
// early-exit if we find something that isn't callable in the union.
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
}
err += "\nWe are unable to determine the appropriate result type for such a call.";
return err;
}
return "Cannot call a value of type " + toString(e.ty);
}
return "Cannot call non-function " + toString(e.ty); return "Cannot call non-function " + toString(e.ty);
} }
std::string operator()(const Luau::ExtraInformation& e) const std::string operator()(const Luau::ExtraInformation& e) const

View File

@ -31,9 +31,7 @@ TypeId Instantiation2::clean(TypeId ty)
// if we didn't learn anything about the lower bound, we pick the upper bound instead. // if we didn't learn anything about the lower bound, we pick the upper bound instead.
// we default to the lower bound which represents the most specific type for the free type. // we default to the lower bound which represents the most specific type for the free type.
TypeId res = get<NeverType>(ft->lowerBound) TypeId res = get<NeverType>(ft->lowerBound) ? ft->upperBound : ft->lowerBound;
? ft->upperBound
: ft->lowerBound;
// Instantiation should not traverse into the type that we are substituting for. // Instantiation should not traverse into the type that we are substituting for.
dontTraverseInto(res); dontTraverseInto(res);

View File

@ -18,6 +18,8 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false) LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false) LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false); LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
LUAU_FASTFLAGVARIABLE(LuauFixCyclicUnionsOfIntersections, false);
// This could theoretically be 2000 on amd64, but x86 requires this. // This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200); LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
@ -29,6 +31,11 @@ static bool fixNormalizeCaching()
return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution; return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution;
} }
static bool fixCyclicUnionsOfIntersections()
{
return FFlag::LuauFixCyclicUnionsOfIntersections || FFlag::DebugLuauDeferredConstraintResolution;
}
namespace Luau namespace Luau
{ {
@ -1768,14 +1775,29 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t
} }
else if (const IntersectionType* itv = get<IntersectionType>(there)) else if (const IntersectionType* itv = get<IntersectionType>(there))
{ {
if (fixCyclicUnionsOfIntersections())
{
if (seenSetTypes.count(there))
return NormalizationResult::True;
seenSetTypes.insert(there);
}
NormalizedType norm{builtinTypes}; NormalizedType norm{builtinTypes};
norm.tops = builtinTypes->anyType; norm.tops = builtinTypes->anyType;
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it) for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
{ {
NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes); NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes);
if (res != NormalizationResult::True) if (res != NormalizationResult::True)
{
if (fixCyclicUnionsOfIntersections())
seenSetTypes.erase(there);
return res; return res;
} }
}
if (fixCyclicUnionsOfIntersections())
seenSetTypes.erase(there);
return unionNormals(here, norm); return unionNormals(here, norm);
} }
else if (get<UnknownType>(here.tops)) else if (get<UnknownType>(here.tops))
@ -3194,6 +3216,13 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
// this is a noop since an intersection with `unknown` is trivial. // this is a noop since an intersection with `unknown` is trivial.
return NormalizationResult::True; return NormalizationResult::True;
} }
else if ((FFlag::LuauNormalizeNotUnknownIntersection || FFlag::DebugLuauDeferredConstraintResolution) && get<UnknownType>(t))
{
// if we're intersecting with `~unknown`, this is equivalent to intersecting with `never`
// this means we should clear the type entirely.
clearNormal(here);
return NormalizationResult::True;
}
else if (auto nt = get<NegationType>(t)) else if (auto nt = get<NegationType>(t))
return intersectNormalWithTy(here, nt->ty, seenSetTypes); return intersectNormalWithTy(here, nt->ty, seenSetTypes);
else else

View File

@ -365,17 +365,9 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`. // we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed. // this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
std::optional<TypeId> selectOverload( std::optional<TypeId> selectOverload(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Normalizer> normalizer,
NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, const Location& location, TypeId fn,
NotNull<TypeArena> arena, TypePackId argsPack)
NotNull<Normalizer> normalizer,
NotNull<Scope> scope,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
const Location& location,
TypeId fn,
TypePackId argsPack
)
{ {
OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location}; OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
auto [status, overload] = resolver.selectOverload(fn, argsPack); auto [status, overload] = resolver.selectOverload(fn, argsPack);
@ -389,17 +381,9 @@ std::optional<TypeId> selectOverload(
return {}; return {};
} }
SolveResult solveFunctionCall( SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,
NotNull<TypeArena> arena, NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, const Location& location, TypeId fn,
NotNull<BuiltinTypes> builtinTypes, TypePackId argsPack)
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope,
const Location& location,
TypeId fn,
TypePackId argsPack
)
{ {
std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack); std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack);
if (!overloadToUse) if (!overloadToUse)

View File

@ -266,7 +266,8 @@ struct ApplyMappedGenerics : Substitution
MappedGenericPacks& mappedGenericPacks; MappedGenericPacks& mappedGenericPacks;
ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks) ApplyMappedGenerics(
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks)
: Substitution(TxnLog::empty(), arena) : Substitution(TxnLog::empty(), arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, arena(arena) , arena(arena)
@ -1310,7 +1311,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
return {isSubclass(subClass, superClass)}; return {isSubclass(subClass, superClass)};
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable) SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable)
{ {
SubtypingResult result{true}; SubtypingResult result{true};
@ -1421,7 +1423,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop
return res; return res;
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm) SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm)
{ {
if (!subNorm || !superNorm) if (!subNorm || !superNorm)
return {false, true}; return {false, true};
@ -1584,15 +1587,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{ {
// Reduce the typefamily instance // Reduce the typefamily instance
auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance); auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance);
// If we return optional, that means the type family was irreducible - we can reduce that to never // If we return optional, that means the type family was irreducible - we can reduce that to never
return isCovariantWith(env, ty, superTy).withErrors(errors); return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
} }
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance)
{ {
// Reduce the typefamily instance // Reduce the typefamily instance
auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance); auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance);
return isCovariantWith(env, subTy, ty).withErrors(errors); return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
} }
/* /*

View File

@ -13,14 +13,8 @@ namespace Luau
static bool isLiteral(const AstExpr* expr) static bool isLiteral(const AstExpr* expr)
{ {
return ( return (expr->is<AstExprTable>() || expr->is<AstExprFunction>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantString>() ||
expr->is<AstExprTable>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNil>());
expr->is<AstExprFunction>() ||
expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantString>() ||
expr->is<AstExprConstantBool>() ||
expr->is<AstExprConstantNil>()
);
} }
// A fast approximation of subTy <: superTy // A fast approximation of subTy <: superTy
@ -91,14 +85,12 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
if (ft && get<SingletonType>(ft->lowerBound)) if (ft && get<SingletonType>(ft->lowerBound))
{ {
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType))
fastIsSubtype(expectedType, builtinTypes->booleanType))
{ {
return ty; return ty;
} }
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound))
fastIsSubtype(expectedType, ft->lowerBound))
{ {
return ty; return ty;
} }
@ -149,11 +141,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
if (expr->is<AstExprConstantString>()) if (expr->is<AstExprConstantString>())
{ {
auto ft = get<FreeType>(exprType); auto ft = get<FreeType>(exprType);
if (ft && if (ft && get<SingletonType>(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
get<SingletonType>(ft->lowerBound) && fastIsSubtype(ft->lowerBound, builtinTypes->stringType))
fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
fastIsSubtype(ft->lowerBound, builtinTypes->stringType)
)
{ {
// if the upper bound is a subtype of the expected type, we can push the expected type in // if the upper bound is a subtype of the expected type, we can push the expected type in
Relation upperBoundRelation = relate(ft->upperBound, expectedType); Relation upperBoundRelation = relate(ft->upperBound, expectedType);
@ -177,11 +166,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
else if (expr->is<AstExprConstantBool>()) else if (expr->is<AstExprConstantBool>())
{ {
auto ft = get<FreeType>(exprType); auto ft = get<FreeType>(exprType);
if (ft && if (ft && get<SingletonType>(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
get<SingletonType>(ft->lowerBound) && fastIsSubtype(ft->lowerBound, builtinTypes->booleanType))
fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)
)
{ {
// if the upper bound is a subtype of the expected type, we can push the expected type in // if the upper bound is a subtype of the expected type, we can push the expected type in
Relation upperBoundRelation = relate(ft->upperBound, expectedType); Relation upperBoundRelation = relate(ft->upperBound, expectedType);
@ -427,4 +413,4 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
return exprType; return exprType;
} }
} } // namespace Luau

View File

@ -1767,12 +1767,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
std::string superStr = tos(c.sourceType); std::string superStr = tos(c.sourceType);
return subStr + " ~ gen " + superStr; return subStr + " ~ gen " + superStr;
} }
else if constexpr (std::is_same_v<T, InstantiationConstraint>)
{
std::string subStr = tos(c.subType);
std::string superStr = tos(c.superType);
return subStr + " ~ inst " + superStr;
}
else if constexpr (std::is_same_v<T, IterableConstraint>) else if constexpr (std::is_same_v<T, IterableConstraint>)
{ {
std::string iteratorStr = tos(c.iterator); std::string iteratorStr = tos(c.iterator);
@ -1822,37 +1816,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
{ {
return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType); return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
} }
else if constexpr (std::is_same_v<T, SingletonOrTopTypeConstraint>)
{
std::string result = tos(c.resultType);
std::string discriminant = tos(c.discriminantType);
if (c.negated)
return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant;
else
return result + " ~ if isSingleton D then D else unknown where D = " + discriminant;
}
else if constexpr (std::is_same_v<T, UnpackConstraint>) else if constexpr (std::is_same_v<T, UnpackConstraint>)
return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack); return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack);
else if constexpr (std::is_same_v<T, Unpack1Constraint>) else if constexpr (std::is_same_v<T, Unpack1Constraint>)
return tos(c.resultType) + " ~ unpack " + tos(c.sourceType); return tos(c.resultType) + " ~ unpack " + tos(c.sourceType);
else if constexpr (std::is_same_v<T, SetOpConstraint>)
{
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
std::string res = tos(c.resultType) + " ~ ";
bool first = true;
for (TypeId t : c.types)
{
if (first)
first = false;
else
res += op;
res += tos(t);
}
return res;
}
else if constexpr (std::is_same_v<T, ReduceConstraint>) else if constexpr (std::is_same_v<T, ReduceConstraint>)
return "reduce " + tos(c.ty); return "reduce " + tos(c.ty);
else if constexpr (std::is_same_v<T, ReducePackConstraint>) else if constexpr (std::is_same_v<T, ReducePackConstraint>)

View File

@ -546,11 +546,13 @@ BlockedType::BlockedType()
{ {
} }
Constraint* BlockedType::getOwner() const { Constraint* BlockedType::getOwner() const
{
return owner; return owner;
} }
void BlockedType::setOwner(Constraint* newOwner) { void BlockedType::setOwner(Constraint* newOwner)
{
LUAU_ASSERT(owner == nullptr); LUAU_ASSERT(owner == nullptr);
if (owner != nullptr) if (owner != nullptr)

View File

@ -94,6 +94,26 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
return allocated; return allocated;
} }
TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::initializer_list<TypeId> types)
{
return addType(TypeFamilyInstanceType{family, std::move(types)});
}
TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
{
return addType(TypeFamilyInstanceType{family, std::move(typeArguments), std::move(packArguments)});
}
TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::initializer_list<TypeId> types)
{
return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(types)});
}
TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
{
return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)});
}
void freeze(TypeArena& arena) void freeze(TypeArena& arena)
{ {
if (!FFlag::DebugLuauFreezeArena) if (!FFlag::DebugLuauFreezeArena)

View File

@ -441,8 +441,8 @@ struct TypeChecker2
return instance; return instance;
seenTypeFamilyInstances.insert(instance); seenTypeFamilyInstances.insert(instance);
ErrorVec errors = reduceFamilies( ErrorVec errors = reduceFamilies(instance, location,
instance, location, TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true) TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
.errors; .errors;
if (!isErrorSuppressing(location, instance)) if (!isErrorSuppressing(location, instance))
reportErrors(std::move(errors)); reportErrors(std::move(errors));
@ -2743,7 +2743,7 @@ struct TypeChecker2
fetch(module->internalTypes.addType(IntersectionType{{tyvar, ty}})); fetch(module->internalTypes.addType(IntersectionType{{tyvar, ty}}));
} }
else else
fetch(tyvar); fetch(follow(tyvar));
if (!normValid) if (!normValid)
break; break;
@ -2871,17 +2871,24 @@ struct TypeChecker2
for (TypeId part : utv) for (TypeId part : utv)
{ {
PropertyType result = hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors); PropertyType result = hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
if (result.present != NormalizationResult::True) if (result.present != NormalizationResult::True)
return {result.present, {}}; return {result.present, {}};
if (result.result) if (result.result)
parts.emplace_back(*result.result); parts.emplace_back(*result.result);
} }
if (parts.size() == 0)
return {NormalizationResult::False, {}};
if (parts.size() == 1)
return {NormalizationResult::True, {parts[0]}};
TypeId propTy; TypeId propTy;
if (context == ValueContext::LValue) if (context == ValueContext::LValue)
module->internalTypes.addType(IntersectionType{parts}); propTy = module->internalTypes.addType(IntersectionType{parts});
else else
module->internalTypes.addType(UnionType{parts}); propTy = module->internalTypes.addType(UnionType{parts});
return {NormalizationResult::True, propTy}; return {NormalizationResult::True, propTy};
} }

View File

@ -217,7 +217,8 @@ struct FamilyReducer
else if (!reduction.uninhabited && !force) else if (!reduction.uninhabited && !force)
{ {
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), reduction.blockedPacks.size()); printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(),
reduction.blockedPacks.size());
for (TypeId b : reduction.blockedTypes) for (TypeId b : reduction.blockedTypes)
result.blockedTypes.insert(b); result.blockedTypes.insert(b);
@ -346,7 +347,8 @@ struct FamilyReducer
return; return;
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}}; TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); TypeFamilyReductionResult<TypeId> result =
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result); handleFamilyReduction(subject, result);
} }
} }
@ -371,7 +373,8 @@ struct FamilyReducer
return; return;
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}}; TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx}); TypeFamilyReductionResult<TypePackId> result =
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result); handleFamilyReduction(subject, result);
} }
} }
@ -385,8 +388,8 @@ struct FamilyReducer
} }
}; };
static FamilyGraphReductionResult reduceFamiliesInternal( static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess,
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force) std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
{ {
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force}; FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
int iterationCount = 0; int iterationCount = 0;
@ -422,7 +425,8 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location 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), std::move(collector.cyclicInstance), location, ctx, force); return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
std::move(collector.cyclicInstance), location, ctx, force);
} }
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force) FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
@ -441,7 +445,8 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
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), std::move(collector.cyclicInstance), location, ctx, force); return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
std::move(collector.cyclicInstance), location, ctx, force);
} }
void TypeFamilyQueue::add(TypeId instanceTy) void TypeFamilyQueue::add(TypeId instanceTy)
@ -461,8 +466,8 @@ bool isPending(TypeId ty, ConstraintSolver* solver)
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty)); return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
} }
TypeFamilyReductionResult<TypeId> notFamilyFn( TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{ {
@ -479,8 +484,8 @@ TypeFamilyReductionResult<TypeId> notFamilyFn(
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> lenFamilyFn( TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{ {
@ -496,17 +501,18 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
return {std::nullopt, false, {operandTy}, {}}; return {std::nullopt, false, {operandTy}, {}};
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy); std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance. // if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
if (!normTy) if (!normTy || inhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}}; return {std::nullopt, false, {}, {}};
// if the operand type is error suppressing, we can immediately reduce to `number`. // if the operand type is error suppressing, we can immediately reduce to `number`.
if (normTy->shouldSuppressErrors()) if (normTy->shouldSuppressErrors())
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
// if we have a `never`, we can never observe that the operator didn't work. // if we have an uninhabited type (like `never`), we can never observe that the operator didn't work.
if (is<NeverType>(operandTy)) if (inhabited == NormalizationResult::False)
return {ctx->builtins->neverType, false, {}, {}}; return {ctx->builtins->neverType, false, {}, {}};
// if we're checking the length of a string, that works! // if we're checking the length of a string, that works!
@ -555,8 +561,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
return {ctx->builtins->numberType, false, {}, {}}; return {ctx->builtins->numberType, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> unmFamilyFn( TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{ {
@ -732,10 +738,10 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
// though there exists no arm of the union that is inhabited or have a reduced type. // though there exists no arm of the union that is inhabited or have a reduced type.
ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?"); ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?");
} }
else if (results.size() == 1)
if (results.size() == 1)
return {results[0], false, {}, {}}; return {results[0], false, {}, {}};
else if (results.size() == 2)
{
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{ TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily}, NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(results), std::move(results),
@ -745,13 +751,6 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
queue->add(resultTy); queue->add(resultTy);
return {resultTy, false, {}, {}}; return {resultTy, false, {}, {}};
} }
else
{
// TODO: We need to generalize `union<...>` type family to be variadic.
TypeId resultTy = ctx->arena->addType(UnionType{std::move(results)});
return {resultTy, false, {}, {}};
}
}
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors. // the necessary state to do that, even if we intend to just eat the errors.
@ -794,8 +793,8 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
return {extracted.head.front(), false, {}, {}}; return {extracted.head.front(), false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> addFamilyFn( TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -806,8 +805,8 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add");
} }
TypeFamilyReductionResult<TypeId> subFamilyFn( TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -818,8 +817,8 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub");
} }
TypeFamilyReductionResult<TypeId> mulFamilyFn( TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -830,8 +829,8 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul");
} }
TypeFamilyReductionResult<TypeId> divFamilyFn( TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -842,8 +841,8 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div");
} }
TypeFamilyReductionResult<TypeId> idivFamilyFn( TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -854,8 +853,8 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv");
} }
TypeFamilyReductionResult<TypeId> powFamilyFn( TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -866,8 +865,8 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow");
} }
TypeFamilyReductionResult<TypeId> modFamilyFn( TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -878,8 +877,8 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod"); return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod");
} }
TypeFamilyReductionResult<TypeId> concatFamilyFn( TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -964,8 +963,8 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(
return {ctx->builtins->stringType, false, {}, {}}; return {ctx->builtins->stringType, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> andFamilyFn( TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1001,8 +1000,8 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(
return {overallResult.result, false, std::move(blockedTypes), {}}; return {overallResult.result, false, std::move(blockedTypes), {}};
} }
TypeFamilyReductionResult<TypeId> orFamilyFn( TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1093,17 +1092,19 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
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);
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
// if either failed to normalize, we can't reduce, but know nothing about inhabitance. // if either failed to normalize, we can't reduce, but know nothing about inhabitance.
if (!normLhsTy || !normRhsTy) if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}}; return {std::nullopt, false, {}, {}};
// if one of the types is error suppressing, we can just go ahead and reduce. // if one of the types is error suppressing, we can just go ahead and reduce.
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors()) if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
// if we have a `never`, we can never observe that the comparison didn't work. // if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work.
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy)) if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
// If both types are some strict subset of `string`, we can reduce now. // If both types are some strict subset of `string`, we can reduce now.
@ -1153,8 +1154,8 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> ltFamilyFn( TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1165,8 +1166,8 @@ TypeFamilyReductionResult<TypeId> ltFamilyFn(
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt"); return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt");
} }
TypeFamilyReductionResult<TypeId> leFamilyFn( TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1177,8 +1178,8 @@ TypeFamilyReductionResult<TypeId> leFamilyFn(
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le"); return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le");
} }
TypeFamilyReductionResult<TypeId> eqFamilyFn( TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1197,9 +1198,11 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(
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);
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
// if either failed to normalize, we can't reduce, but know nothing about inhabitance. // if either failed to normalize, we can't reduce, but know nothing about inhabitance.
if (!normLhsTy || !normRhsTy) if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}}; return {std::nullopt, false, {}, {}};
// if one of the types is error suppressing, we can just go ahead and reduce. // if one of the types is error suppressing, we can just go ahead and reduce.
@ -1207,7 +1210,7 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
// if we have a `never`, we can never observe that the comparison didn't work. // if we have a `never`, we can never observe that the comparison didn't work.
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy)) if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
return {ctx->builtins->booleanType, false, {}, {}}; return {ctx->builtins->booleanType, false, {}, {}};
// findMetatableEntry demands the ability to emit errors, so we must give it // findMetatableEntry demands the ability to emit errors, so we must give it
@ -1282,8 +1285,8 @@ struct FindRefinementBlockers : TypeOnceVisitor
}; };
TypeFamilyReductionResult<TypeId> refineFamilyFn( TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 2 || !packParams.empty())
{ {
@ -1340,73 +1343,145 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(
return {resultTy, false, {}, {}}; return {resultTy, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> unionFamilyFn( TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{
ctx->ice->ice("singleton type family: encountered a type family instance without the required argument structure");
LUAU_ASSERT(false);
}
TypeId type = follow(typeParams.at(0));
// check to see if both operand types are resolved enough, and wait to reduce if not
if (isPending(type, ctx->solver))
return {std::nullopt, false, {type}, {}};
TypeId followed = type;
// we want to follow through a negation here as well.
if (auto negation = get<NegationType>(followed))
followed = follow(negation->ty);
// if we have a singleton type or `nil`, which is its own singleton type...
if (get<SingletonType>(followed) || isNil(followed))
return {type, false, {}, {}};
// otherwise, we'll return the top type, `unknown`.
return {ctx->builtins->unknownType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (!packParams.empty())
{ {
ctx->ice->ice("union type family: encountered a type family instance without the required argument structure"); ctx->ice->ice("union type family: encountered a type family instance without the required argument structure");
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
TypeId lhsTy = follow(typeParams.at(0)); // if we only have one parameter, there's nothing to do.
TypeId rhsTy = follow(typeParams.at(1)); if (typeParams.size() == 1)
return {follow(typeParams[0]), false, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not // we need to follow all of the type parameters.
if (isPending(lhsTy, ctx->solver)) std::vector<TypeId> types;
return {std::nullopt, false, {lhsTy}, {}}; types.reserve(typeParams.size());
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore for (auto ty : typeParams)
return {rhsTy, false, {}, {}}; types.emplace_back(follow(ty));
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, false, {rhsTy}, {}};
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
return {lhsTy, false, {}, {}};
// unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type.
// this also will early return if _everything_ is `never`, since we already have to check that.
std::optional<TypeId> lastType = std::nullopt;
for (auto ty : types)
{
// if we have a previous type and it's not `never` and the current type isn't `never`...
if (lastType && !get<NeverType>(lastType) && !get<NeverType>(ty))
{
// we know we are not taking the short-circuited path.
lastType = std::nullopt;
break;
}
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy); if (get<NeverType>(ty))
continue;
lastType = ty;
}
// if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
if (lastType)
return {lastType, false, {}, {}};
// check to see if the operand types are resolved enough, and wait to reduce if not
for (auto ty : types)
if (isPending(ty, ctx->solver))
return {std::nullopt, false, {ty}, {}};
// fold over the types with `simplifyUnion`
TypeId resultTy = ctx->builtins->neverType;
for (auto ty : types)
{
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty()) if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
return {result.result, false, {}, {}}; resultTy = result.result;
}
return {resultTy, false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> intersectFamilyFn( TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 2 || !packParams.empty()) if (!packParams.empty())
{ {
ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure"); ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure");
LUAU_ASSERT(false); LUAU_ASSERT(false);
} }
TypeId lhsTy = follow(typeParams.at(0)); // if we only have one parameter, there's nothing to do.
TypeId rhsTy = follow(typeParams.at(1)); if (typeParams.size() == 1)
return {follow(typeParams[0]), false, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not // we need to follow all of the type parameters.
if (isPending(lhsTy, ctx->solver)) std::vector<TypeId> types;
return {std::nullopt, false, {lhsTy}, {}}; types.reserve(typeParams.size());
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore for (auto ty : typeParams)
return {ctx->builtins->neverType, false, {}, {}}; types.emplace_back(follow(ty));
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, false, {rhsTy}, {}};
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
return {ctx->builtins->neverType, false, {}, {}};
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy); // check to see if the operand types are resolved enough, and wait to reduce if not
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
for (auto ty : types)
{
if (isPending(ty, ctx->solver))
return {std::nullopt, false, {ty}, {}};
else if (get<NeverType>(ty))
return {ctx->builtins->neverType, false, {}, {}};
}
// fold over the types with `simplifyIntersection`
TypeId resultTy = ctx->builtins->unknownType;
for (auto ty : types)
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty()) if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
resultTy = result.result;
}
// if the intersection simplifies to `never`, this gives us bad autocomplete. // if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable // we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail. // if we ever give `never` some kind of "explanation" trail.
if (get<NeverType>(result.result)) if (get<NeverType>(resultTy))
{ {
TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}}); TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
return {intersection, false, {}, {}}; return {intersection, false, {}, {}};
} }
return {result.result, false, {}, {}}; return {resultTy, false, {}, {}};
} }
// computes the keys of `ty` into `result` // computes the keys of `ty` into `result`
@ -1581,8 +1656,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}}; return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
} }
TypeFamilyReductionResult<TypeId> keyofFamilyFn( TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{ {
@ -1593,8 +1668,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyFn(
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false); return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
} }
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn( TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx) const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{ {
if (typeParams.size() != 1 || !packParams.empty()) if (typeParams.size() != 1 || !packParams.empty())
{ {
@ -1623,6 +1698,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
, leFamily{"le", leFamilyFn} , leFamily{"le", leFamilyFn}
, eqFamily{"eq", eqFamilyFn} , eqFamily{"eq", eqFamilyFn}
, refineFamily{"refine", refineFamilyFn} , refineFamily{"refine", refineFamilyFn}
, singletonFamily{"singleton", singletonFamilyFn}
, unionFamily{"union", unionFamilyFn} , unionFamily{"union", unionFamilyFn}
, intersectFamily{"intersect", intersectFamilyFn} , intersectFamily{"intersect", intersectFamilyFn}
, keyofFamily{"keyof", keyofFamilyFn} , keyofFamily{"keyof", keyofFamilyFn}

View File

@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false) LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMetatableInstantiationCloneCheck, false)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false) LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false) LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false) LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
@ -5632,7 +5633,8 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
TypeId instantiated = *maybeInstantiated; TypeId instantiated = *maybeInstantiated;
TypeId target = follow(instantiated); TypeId target = follow(instantiated);
bool needsClone = follow(tf.type) == target; const TableType* tfTable = FFlag::LuauMetatableInstantiationCloneCheck ? getTableType(tf.type) : nullptr;
bool needsClone = follow(tf.type) == target || (FFlag::LuauMetatableInstantiationCloneCheck && tfTable != nullptr && tfTable == getTableType(target));
bool shouldMutate = getTableType(tf.type); bool shouldMutate = getTableType(tf.type);
TableType* ttv = getMutableTableType(target); TableType* ttv = getMutableTableType(target);

View File

@ -6,8 +6,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauFollowEmptyTypePacks, false);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
namespace Luau namespace Luau
@ -271,8 +269,7 @@ TypePackId follow(TypePackId tp, const void* context, TypePackId (*mapper)(const
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped)) if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped))
return btv->boundTo; return btv->boundTo;
else if (const TypePack* tp = get<TypePack>(mapped); else if (const TypePack* tp = get<TypePack>(mapped); tp && tp->head.empty())
(FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauFollowEmptyTypePacks) && tp && tp->head.empty())
return tp->tail; return tp->tail;
else else
return std::nullopt; return std::nullopt;

View File

@ -52,6 +52,11 @@ bool Index::operator==(const Index& other) const
return index == other.index; return index == other.index;
} }
bool Reduction::operator==(const Reduction& other) const
{
return resultType == other.resultType;
}
Path Path::append(const Path& suffix) const Path Path::append(const Path& suffix) const
{ {
std::vector<Component> joined(components); std::vector<Component> joined(components);
@ -124,6 +129,11 @@ size_t PathHash::operator()(const PackField& field) const
return static_cast<size_t>(field); return static_cast<size_t>(field);
} }
size_t PathHash::operator()(const Reduction& reduction) const
{
return std::hash<TypeId>()(reduction.resultType);
}
size_t PathHash::operator()(const Component& component) const size_t PathHash::operator()(const Component& component) const
{ {
return visit(*this, component); return visit(*this, component);
@ -472,6 +482,14 @@ struct TraversalState
return false; return false;
} }
bool traverse(TypePath::Reduction reduction)
{
if (checkInvariants())
return false;
updateCurrent(reduction.resultType);
return true;
}
bool traverse(TypePath::PackField field) bool traverse(TypePath::PackField field)
{ {
if (checkInvariants()) if (checkInvariants())
@ -584,9 +602,14 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
result << "tail"; result << "tail";
break; break;
} }
result << "()"; result << "()";
} }
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
{
// We need to rework the TypePath system to make subtyping failures easier to understand
// https://roblox.atlassian.net/browse/CLI-104422
result << "~~>";
}
else else
{ {
static_assert(always_false_v<T>, "Unhandled Component variant"); static_assert(always_false_v<T>, "Unhandled Component variant");

View File

@ -1006,7 +1006,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
if (!subNorm || !superNorm) if (!subNorm || !superNorm)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); tryUnifyNormalizedTypes(
subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
else else
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
} }
@ -1017,7 +1018,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
if (!subNorm || !superNorm) if (!subNorm || !superNorm)
reportError(location, NormalizationTooComplex{}); reportError(location, NormalizationTooComplex{});
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption) else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption); tryUnifyNormalizedTypes(
subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
else else
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible"); tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
} }

View File

@ -42,9 +42,7 @@ static bool areCompatible(TypeId left, TypeId right)
LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared()); LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
const TypeId leftType = follow( const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type());
leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()
);
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value()) if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
return true; return true;
@ -75,6 +73,18 @@ static bool areCompatible(TypeId left, TypeId right)
return true; return true;
} }
// returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`.
static bool isIrresolvable(TypeId ty)
{
return get<BlockedType>(ty) || get<TypeFamilyInstanceType>(ty);
}
// returns `true` if `tp` is irressolvable and should be added to `incompleteSubtypes`.
static bool isIrresolvable(TypePackId tp)
{
return get<BlockedTypePack>(tp) || get<TypeFamilyInstanceTypePack>(tp);
}
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)
: arena(arena) : arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
@ -82,6 +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)
{
}
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice,
DenseHashSet<const void*>* uninhabitedTypeFamilies)
: arena(arena)
, builtinTypes(builtinTypes)
, scope(scope)
, ice(ice)
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
, uninhabitedTypeFamilies(uninhabitedTypeFamilies)
{ {
} }
@ -110,8 +133,11 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
// But we exclude these two subtyping patterns, they are tautological: // But we exclude these two subtyping patterns, they are tautological:
// - never <: *blocked* // - never <: *blocked*
// - *blocked* <: unknown // - *blocked* <: unknown
if ((get<BlockedType>(subTy) || get<BlockedType>(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)))
return true;
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
return true; return true;
} }
@ -473,8 +499,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
if (subTp == superTp) if (subTp == superTp)
return true; return true;
if (get<BlockedTypePack>(subTp) || get<BlockedTypePack>(superTp)) if (isIrresolvable(subTp) || isIrresolvable(superTp))
{ {
if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTp) || uninhabitedTypeFamilies->contains(superTp)))
return true;
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
return true; return true;
} }

View File

@ -7,6 +7,8 @@
#include "lobject.h" #include "lobject.h"
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
namespace Luau namespace Luau
{ {
namespace CodeGen namespace CodeGen
@ -833,6 +835,27 @@ void analyzeBytecodeTypes(IrFunction& function)
bcType.result = regTags[ra]; bcType.result = regTags[ra];
break; break;
} }
case LOP_NAMECALL:
{
if (FFlag::LuauCodegenDirectUserdataFlow)
{
int ra = LUAU_INSN_A(*pc);
int rb = LUAU_INSN_B(*pc);
uint32_t kc = pc[1];
bcType.a = regTags[rb];
bcType.b = getBytecodeConstantTag(proto, kc);
// While namecall might result in a callable table, we assume the function fast path
regTags[ra] = LBC_TYPE_FUNCTION;
// Namecall places source register into target + 1
regTags[ra + 1] = bcType.a;
bcType.result = LBC_TYPE_FUNCTION;
}
break;
}
case LOP_GETGLOBAL: case LOP_GETGLOBAL:
case LOP_SETGLOBAL: case LOP_SETGLOBAL:
case LOP_CALL: case LOP_CALL:
@ -866,7 +889,6 @@ void analyzeBytecodeTypes(IrFunction& function)
case LOP_COVERAGE: case LOP_COVERAGE:
case LOP_GETIMPORT: case LOP_GETIMPORT:
case LOP_CAPTURE: case LOP_CAPTURE:
case LOP_NAMECALL:
case LOP_PREPVARARGS: case LOP_PREPVARARGS:
case LOP_GETVARARGS: case LOP_GETVARARGS:
case LOP_FORGPREP: case LOP_FORGPREP:

View File

@ -163,6 +163,9 @@ bool isUnwindSupported()
{ {
#if defined(_WIN32) && defined(_M_X64) #if defined(_WIN32) && defined(_M_X64)
return true; return true;
#elif defined(__ANDROID__)
// Current unwind information is not compatible with Android
return false;
#elif defined(__APPLE__) && defined(__aarch64__) #elif defined(__APPLE__) && defined(__aarch64__)
char ver[256]; char ver[256];
size_t verLength = sizeof(ver); size_t verLength = sizeof(ver);

View File

@ -122,7 +122,10 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
if (stats && (stats->functionStatsFlags & FunctionStats_Enable)) if (stats && (stats->functionStatsFlags & FunctionStats_Enable))
{ {
FunctionStats functionStat; FunctionStats functionStat;
functionStat.name = p->debugname ? getstr(p->debugname) : "";
// function name is empty for anonymous and pseudo top-level functions
// properly name pseudo top-level function because it will be compiled natively if it has loops
functionStat.name = p->debugname ? getstr(p->debugname) : p->bytecodeid == root->bytecodeid ? "[top level]" : "[anonymous]";
functionStat.line = p->linedefined; functionStat.line = p->linedefined;
functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode); functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode);
functionStat.irCount = unsigned(ir.function.instructions.size()); functionStat.irCount = unsigned(ir.function.instructions.size());

View File

@ -13,6 +13,7 @@
#include "ltm.h" #include "ltm.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false) LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
namespace Luau namespace Luau
{ {
@ -1230,6 +1231,14 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
return; return;
} }
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
{
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
return;
}
IrOp fallback = build.block(IrBlockKind::Fallback); IrOp fallback = build.block(IrBlockKind::Fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
@ -1256,10 +1265,20 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
int rb = LUAU_INSN_B(*pc); int rb = LUAU_INSN_B(*pc);
uint32_t aux = pc[1]; uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb)); IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
{
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
build.inst(IrCmd::FALLBACK_SETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
return;
}
IrOp fallback = build.block(IrBlockKind::Fallback);
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback); build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
@ -1370,12 +1389,31 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
int rb = LUAU_INSN_B(*pc); int rb = LUAU_INSN_B(*pc);
uint32_t aux = pc[1]; uint32_t aux = pc[1];
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_VECTOR)
{
build.loadAndCheckTag(build.vmReg(rb), LUA_TVECTOR, build.vmExit(pcpos));
build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
return;
}
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
{
build.loadAndCheckTag(build.vmReg(rb), LUA_TUSERDATA, build.vmExit(pcpos));
build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
return;
}
IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL)); IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL));
IrOp fallback = build.block(IrBlockKind::Fallback); IrOp fallback = build.block(IrBlockKind::Fallback);
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal); IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
IrOp secondFastPath = build.block(IrBlockKind::Internal); IrOp secondFastPath = build.block(IrBlockKind::Internal);
build.loadAndCheckTag(build.vmReg(rb), LUA_TTABLE, fallback); build.loadAndCheckTag(
build.vmReg(rb), LUA_TTABLE, FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb)); IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
CODEGEN_ASSERT(build.function.proto); CODEGEN_ASSERT(build.function.proto);

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019-2023 Roblox Corporation Copyright (c) 2019-2024 Roblox Corporation
Copyright (c) 19942019 Lua.org, PUC-Rio. Copyright (c) 19942019 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of

View File

@ -929,7 +929,7 @@ static size_t gcstep(lua_State* L, size_t limit)
{ {
while (g->sweepgcopage && cost < limit) while (g->sweepgcopage && cost < limit)
{ {
lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page lua_Page* next = luaM_getnextpage(g->sweepgcopage); // page sweep might destroy the page
int steps = sweepgcopage(L, g->sweepgcopage); int steps = sweepgcopage(L, g->sweepgcopage);

View File

@ -196,9 +196,9 @@ struct lua_Page
lua_Page* prev; lua_Page* prev;
lua_Page* next; lua_Page* next;
// list of all gco pages // list of all pages
lua_Page* gcolistprev; lua_Page* listprev;
lua_Page* gcolistnext; lua_Page* listnext;
int pageSize; // page size in bytes, including page header int pageSize; // page size in bytes, including page header
int blockSize; // block size in bytes, including block header (for non-GCO) int blockSize; // block size in bytes, including block header (for non-GCO)
@ -220,7 +220,7 @@ l_noret luaM_toobig(lua_State* L)
luaG_runerror(L, "memory allocation error: block too big"); luaG_runerror(L, "memory allocation error: block too big");
} }
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount) static lua_Page* newpage(lua_State* L, lua_Page** pageset, int pageSize, int blockSize, int blockCount)
{ {
global_State* g = L->global; global_State* g = L->global;
@ -236,8 +236,8 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
page->prev = NULL; page->prev = NULL;
page->next = NULL; page->next = NULL;
page->gcolistprev = NULL; page->listprev = NULL;
page->gcolistnext = NULL; page->listnext = NULL;
page->pageSize = pageSize; page->pageSize = pageSize;
page->blockSize = blockSize; page->blockSize = blockSize;
@ -249,12 +249,12 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
page->freeNext = (blockCount - 1) * blockSize; page->freeNext = (blockCount - 1) * blockSize;
page->busyBlocks = 0; page->busyBlocks = 0;
if (gcopageset) if (pageset)
{ {
page->gcolistnext = *gcopageset; page->listnext = *pageset;
if (page->gcolistnext) if (page->listnext)
page->gcolistnext->gcolistprev = page; page->listnext->listprev = page;
*gcopageset = page; *pageset = page;
} }
return page; return page;
@ -263,7 +263,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
// this is part of a cold path in newblock and newgcoblock // this is part of a cold path in newblock and newgcoblock
// it is marked as noinline to prevent it from being inlined into those functions // it is marked as noinline to prevent it from being inlined into those functions
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance // if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata) LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, uint8_t sizeClass, bool storeMetadata)
{ {
if (FFlag::LuauExtendedSizeClasses) if (FFlag::LuauExtendedSizeClasses)
{ {
@ -272,7 +272,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0); int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize; int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize;
lua_Page* page = newpage(L, gcopageset, pageSize, blockSize, blockCount); lua_Page* page = newpage(L, pageset, pageSize, blockSize, blockCount);
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!freepageset[sizeClass]); LUAU_ASSERT(!freepageset[sizeClass]);
@ -285,7 +285,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0); int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize; int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize;
lua_Page* page = newpage(L, gcopageset, kSmallPageSize, blockSize, blockCount); lua_Page* page = newpage(L, pageset, kSmallPageSize, blockSize, blockCount);
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!) // prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
LUAU_ASSERT(!freepageset[sizeClass]); LUAU_ASSERT(!freepageset[sizeClass]);
@ -295,27 +295,27 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
} }
} }
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page) static void freepage(lua_State* L, lua_Page** pageset, lua_Page* page)
{ {
global_State* g = L->global; global_State* g = L->global;
if (gcopageset) if (pageset)
{ {
// remove page from alllist // remove page from alllist
if (page->gcolistnext) if (page->listnext)
page->gcolistnext->gcolistprev = page->gcolistprev; page->listnext->listprev = page->listprev;
if (page->gcolistprev) if (page->listprev)
page->gcolistprev->gcolistnext = page->gcolistnext; page->listprev->listnext = page->listnext;
else if (*gcopageset == page) else if (*pageset == page)
*gcopageset = page->gcolistnext; *pageset = page->listnext;
} }
// so long // so long
(*g->frealloc)(g->ud, page, page->pageSize, 0); (*g->frealloc)(g->ud, page, page->pageSize, 0);
} }
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass) static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, lua_Page* page, uint8_t sizeClass)
{ {
// remove page from freelist // remove page from freelist
if (page->next) if (page->next)
@ -326,7 +326,7 @@ static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopa
else if (freepageset[sizeClass] == page) else if (freepageset[sizeClass] == page)
freepageset[sizeClass] = page->next; freepageset[sizeClass] = page->next;
freepage(L, gcopageset, page); freepage(L, pageset, page);
} }
static void* newblock(lua_State* L, int sizeClass) static void* newblock(lua_State* L, int sizeClass)
@ -645,9 +645,9 @@ void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blo
*pageSize = page->pageSize; *pageSize = page->pageSize;
} }
lua_Page* luaM_getnextgcopage(lua_Page* page) lua_Page* luaM_getnextpage(lua_Page* page)
{ {
return page->gcolistnext; return page->listnext;
} }
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)) void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
@ -684,7 +684,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l
for (lua_Page* curr = g->allgcopages; curr;) for (lua_Page* curr = g->allgcopages; curr;)
{ {
lua_Page* next = curr->gcolistnext; // block visit might destroy the page lua_Page* next = curr->listnext; // block visit might destroy the page
luaM_visitpage(curr, context, visitor); luaM_visitpage(curr, context, visitor);

View File

@ -27,7 +27,7 @@ LUAI_FUNC l_noret luaM_toobig(lua_State* L);
LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize); LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize);
LUAI_FUNC void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blockSize, int* pageSize); LUAI_FUNC void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blockSize, int* pageSize);
LUAI_FUNC lua_Page* luaM_getnextgcopage(lua_Page* page); LUAI_FUNC lua_Page* luaM_getnextpage(lua_Page* page);
LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)); LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco)); LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));

View File

@ -3,6 +3,7 @@
#include "lualib.h" #include "lualib.h"
#include "lapi.h" #include "lapi.h"
#include "lnumutils.h"
#include "lstate.h" #include "lstate.h"
#include "ltable.h" #include "ltable.h"
#include "lstring.h" #include "lstring.h"
@ -10,6 +11,8 @@
#include "ldebug.h" #include "ldebug.h"
#include "lvm.h" #include "lvm.h"
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false)
static int foreachi(lua_State* L) static int foreachi(lua_State* L)
{ {
luaL_checktype(L, 1, LUA_TTABLE); luaL_checktype(L, 1, LUA_TTABLE);
@ -112,6 +115,68 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t)
luaC_barrierfast(L, dst); luaC_barrierfast(L, dst);
} }
else if (DFFlag::LuauFastCrossTableMove && dst != src)
{
// compute the array slice we have to copy over
int slicestart = f < 1 ? 0 : (f > src->sizearray ? src->sizearray : f - 1);
int sliceend = e < 1 ? 0 : (e > src->sizearray ? src->sizearray : e);
LUAU_ASSERT(slicestart <= sliceend);
int slicecount = sliceend - slicestart;
if (slicecount > 0)
{
// array slice starting from INT_MIN is impossible, so we don't have to worry about int overflow
int dstslicestart = f < 1 ? -f + 1 : 0;
// copy over the slice
for (int i = 0; i < slicecount; ++i)
{
lua_rawgeti(L, srct, slicestart + i + 1);
lua_rawseti(L, dstt, dstslicestart + t + i);
}
}
// copy the remaining elements that could be in the hash part
int hashpartsize = sizenode(src);
// select the strategy with the least amount of steps
if (n <= hashpartsize)
{
for (int i = 0; i < n; ++i)
{
// skip array slice elements that were already copied over
if (cast_to(unsigned int, f + i - 1) < cast_to(unsigned int, src->sizearray))
continue;
lua_rawgeti(L, srct, f + i);
lua_rawseti(L, dstt, t + i);
}
}
else
{
// source and destination tables are different, so we can iterate over source hash part directly
int i = hashpartsize;
while (i--)
{
LuaNode* node = gnode(src, i);
if (ttisnumber(gkey(node)))
{
double n = nvalue(gkey(node));
int k;
luai_num2int(k, n);
if (luai_numeq(cast_num(k), n) && k >= f && k <= e)
{
lua_rawgeti(L, srct, k);
lua_rawseti(L, dstt, t - f + k);
}
}
}
}
}
else else
{ {
if (t > e || t <= f || dst != src) if (t > e || t <= f || dst != src)

View File

@ -145,7 +145,8 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
{ {
}; };
#define LUAU_CHECK_HAS_KEY(map, key) do \ #define LUAU_CHECK_HAS_KEY(map, key) \
do \
{ \ { \
auto&& _m = (map); \ auto&& _m = (map); \
auto&& _k = (key); \ auto&& _k = (key); \
@ -161,7 +162,8 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
} \ } \
} while (false) } while (false)
#define LUAU_CHECK_HAS_NO_KEY(map, key) do \ #define LUAU_CHECK_HAS_NO_KEY(map, key) \
do \
{ \ { \
auto&& _m = (map); \ auto&& _m = (map); \
auto&& _k = (key); \ auto&& _k = (key); \

View File

@ -29,8 +29,7 @@ ClassFixture::ClassFixture()
}; };
getMutable<ClassType>(connectionType)->props = { getMutable<ClassType>(connectionType)->props = {
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}} {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}};
};
TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"}); TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
getMutable<ClassType>(baseClassType)->props = { getMutable<ClassType>(baseClassType)->props = {
@ -103,13 +102,10 @@ ClassFixture::ClassFixture()
}; };
getMutable<TableType>(vector2MetaType)->props = { getMutable<TableType>(vector2MetaType)->props = {
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}}, {"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
{"__mul", { {"__mul", {arena.addType(IntersectionType{{
arena.addType(IntersectionType{{
makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}),
makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}), makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}),
}}) }})}}};
}}
};
globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
addGlobalBinding(globals, "Vector2", vector2Type, "@test"); addGlobalBinding(globals, "Vector2", vector2Type, "@test");

View File

@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
LUAU_FASTFLAG(LuauCodegenInferNumTag) LUAU_FASTFLAG(LuauCodegenInferNumTag)
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult) LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB) LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
LUAU_DYNAMIC_FASTFLAG(LuauFastCrossTableMove)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -415,6 +416,8 @@ TEST_CASE("Sort")
TEST_CASE("Move") TEST_CASE("Move")
{ {
ScopedFastFlag luauFastCrossTableMove{DFFlag::LuauFastCrossTableMove, true};
runConformance("move.lua"); runConformance("move.lua");
} }
@ -2174,8 +2177,7 @@ TEST_CASE("HugeFunctionLoadFailure")
static size_t largeAllocationToFail = 0; static size_t largeAllocationToFail = 0;
static size_t largeAllocationCount = 0; static size_t largeAllocationCount = 0;
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* {
{
if (nsize == 0) if (nsize == 0)
{ {
free(ptr); free(ptr);

View File

@ -19,12 +19,10 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation")
TypeId genericT = arena.addType(GenericType{"T"}); TypeId genericT = arena.addType(GenericType{"T"});
TypeId idTy = arena.addType(FunctionType{ TypeId idTy = arena.addType(FunctionType{/* generics */ {genericT},
/* generics */ {genericT},
/* genericPacks */ {}, /* genericPacks */ {},
/* argTypes */ arena.addTypePack({genericT}), /* argTypes */ arena.addTypePack({genericT}),
/* retTypes */ arena.addTypePack({genericT}) /* retTypes */ arena.addTypePack({genericT})});
});
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr}; DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr}; DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};

View File

@ -14,6 +14,7 @@
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5) LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenLoadTVTag) LUAU_FASTFLAG(LuauCodegenLoadTVTag)
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
static std::string getCodegenAssembly(const char* source) static std::string getCodegenAssembly(const char* source)
{ {
@ -420,4 +421,128 @@ bb_bytecode_1:
)"); )");
} }
TEST_CASE("VectorNamecall")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function abs(a: vector)
return a:Abs()
end
)"),
R"(
; function abs($arg0) line 2
bb_0:
CHECK_TAG R0, tvector, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_NAMECALL 0u, R1, R0, K0
INTERRUPT 2u
SET_SAVEDPC 3u
CALL R1, 1i, -1i
INTERRUPT 3u
RETURN R1, -1i
)");
}
TEST_CASE("UserDataGetIndex")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a.x + a.y
end
)"),
R"(
; function getxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_GETTABLEKS 0u, R2, R0, K0
FALLBACK_GETTABLEKS 2u, R3, R0, K1
CHECK_TAG R2, tnumber, bb_fallback_3
CHECK_TAG R3, tnumber, bb_fallback_3
%14 = LOAD_DOUBLE R2
%16 = ADD_NUM %14, R3
STORE_DOUBLE R1, %16
STORE_TAG R1, tnumber
JUMP bb_4
bb_4:
INTERRUPT 5u
RETURN R1, 1i
)");
}
TEST_CASE("UserDataSetIndex")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function setxy(a: Point)
a.x = 3
a.y = 4
end
)"),
R"(
; function setxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
STORE_DOUBLE R1, 3
STORE_TAG R1, tnumber
FALLBACK_SETTABLEKS 1u, R1, R0, K0
STORE_DOUBLE R1, 4
FALLBACK_SETTABLEKS 4u, R1, R0, K1
INTERRUPT 6u
RETURN R0, 0i
)");
}
TEST_CASE("UserDataNamecall")
{
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function getxy(a: Point)
return a:GetX() + a:GetY()
end
)"),
R"(
; function getxy($arg0) line 2
bb_0:
CHECK_TAG R0, tuserdata, exit(entry)
JUMP bb_2
bb_2:
JUMP bb_bytecode_1
bb_bytecode_1:
FALLBACK_NAMECALL 0u, R2, R0, K0
INTERRUPT 2u
SET_SAVEDPC 3u
CALL R2, 1i, 1i
FALLBACK_NAMECALL 3u, R3, R0, K1
INTERRUPT 5u
SET_SAVEDPC 6u
CALL R3, 1i, 1i
CHECK_TAG R2, tnumber, bb_fallback_3
CHECK_TAG R3, tnumber, bb_fallback_3
%20 = LOAD_DOUBLE R2
%22 = ADD_NUM %20, R3
STORE_DOUBLE R1, %22
STORE_TAG R1, tnumber
JUMP bb_4
bb_4:
INTERRUPT 7u
RETURN R1, 1i
)");
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -11,7 +11,9 @@
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauFixNormalizeCaching); LUAU_FASTFLAG(LuauFixNormalizeCaching)
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
LUAU_FASTFLAG(LuauFixCyclicUnionsOfIntersections);
using namespace Luau; using namespace Luau;
@ -797,6 +799,36 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union")
CHECK("number" == toString(normalizer.typeFromNormal(*nt))); CHECK("number" == toString(normalizer.typeFromNormal(*nt)));
} }
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection")
{
ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true};
// t1 where t1 = (string & t1) | string
TypeId boundTy = arena.addType(BlockedType{});
TypeId intersectTy = arena.addType(IntersectionType{{builtinTypes->stringType, boundTy}});
TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, intersectTy}});
asMutable(boundTy)->reassign(Type{BoundType{unionTy}});
std::shared_ptr<const NormalizedType> nt = normalizer.normalize(unionTy);
CHECK("string" == toString(normalizer.typeFromNormal(*nt)));
}
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions")
{
ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true};
// t1 where t1 = (string & t1) | string
TypeId boundTy = arena.addType(BlockedType{});
TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, boundTy}});
TypeId intersectionTy = arena.addType(IntersectionType{{builtinTypes->stringType, unionTy}});
asMutable(boundTy)->reassign(Type{BoundType{intersectionTy}});
std::shared_ptr<const NormalizedType> nt = normalizer.normalize(intersectionTy);
CHECK("string" == toString(normalizer.typeFromNormal(*nt)));
}
TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable") TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
{ {
CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>"))); CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>")));
@ -919,4 +951,15 @@ TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_n
CHECK(na1 != na2); CHECK(na1 != na2);
} }
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
{
ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true};
TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType});
TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}});
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(type);
CHECK("never" == toString(normalizer.typeFromNormal(*normalized.get())));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -1285,6 +1285,30 @@ 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")
{
TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType,
builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}});
TypeId tblTy = tbl({{"depth", builtinTypes->unknownType}});
TypeId combined = meet(longTy, tblTy);
TypeId subTy = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.unionFamily}, {combined, builtinTypes->neverType}, {}});
TypeId superTy = builtinTypes->neverType;
SubtypingResult result = isSubtype(subTy, superTy);
CHECK(!result.isSubtype);
for (const SubtypingReasoning& reasoning : result.reasoning)
{
if (reasoning.subPath.empty() && reasoning.superPath.empty())
continue;
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes);
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes);
if (!optSubLeaf || !optSuperLeaf)
CHECK(false);
}
}
TEST_SUITE_END(); TEST_SUITE_END();
TEST_SUITE_BEGIN("Subtyping.Subpaths"); TEST_SUITE_BEGIN("Subtyping.Subpaths");

View File

@ -944,7 +944,8 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
std::string expected; std::string expected;
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
expected = R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)"; expected =
R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
else else
expected = R"(Type expected = R"(Type
'{ a: number, b: string, c: { d: string } }' '{ a: number, b: string, c: { d: string } }'
@ -1013,15 +1014,8 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack")
TypePack* packPtr = getMutable<TypePack>(thePack); TypePack* packPtr = getMutable<TypePack>(thePack);
REQUIRE(packPtr); REQUIRE(packPtr);
const TableType::Props theProps = { const TableType::Props theProps = {{"BaseField", Property::readonly(builtinTypes->unknownType)},
{"BaseField", Property::readonly(builtinTypes->unknownType)}, {"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))}};
{"BaseMethod", Property::readonly(arena.addType(
FunctionType{
thePack,
arena.addTypePack({})
}
))}
};
TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed}); TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed});

View File

@ -221,7 +221,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)"; const std::string expected =
R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}}); CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));

View File

@ -10,7 +10,6 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution); LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauSetMetatableOnUnionsOfTables);
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -371,8 +370,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly")
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables") TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
{ {
ScopedFastFlag sff{FFlag::LuauSetMetatableOnUnionsOfTables, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type A = {tag: "A", x: number} type A = {tag: "A", x: number}
type B = {tag: "B", y: string} type B = {tag: "B", y: string}

View File

@ -648,32 +648,20 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties")
unfreeze(arena); unfreeze(arena);
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"}); TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
getMutable<ClassType>(instanceType)->props = { getMutable<ClassType>(instanceType)->props = {{"Parent", Property::rw(instanceType)}};
{"Parent", Property::rw(instanceType)}
};
// //
TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"}); TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
TypeId scriptType = arena.addType(ClassType{ TypeId scriptType =
"Script", { arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test"});
{"Parent", Property::rw(workspaceType, instanceType)}
},
instanceType, nullopt, {}, {}, "Test"
});
TypeId partType = arena.addType(ClassType{ TypeId partType = arena.addType(
"Part", { ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}},
{"BrickColor", Property::rw(builtinTypes->stringType)},
{"Parent", Property::rw(workspaceType, instanceType)}
},
instanceType, nullopt, {}, {}, "Test"}); instanceType, nullopt, {}, {}, "Test"});
getMutable<ClassType>(workspaceType)->props = { getMutable<ClassType>(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}};
{"Script", Property::readonly(scriptType)},
{"Part", Property::readonly(partType)}
};
frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType}; frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType};
@ -703,7 +691,8 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_MESSAGE(get<DynamicPropertyLookupOnClassesUnsafe>(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]); CHECK_MESSAGE(
get<DynamicPropertyLookupOnClassesUnsafe>(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]);
CHECK(builtinTypes->errorType == requireType("c")); CHECK(builtinTypes->errorType == requireType("c"));
} }

View File

@ -20,6 +20,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls); LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTINT(LuauTarjanChildLimit); LUAU_FASTINT(LuauTarjanChildLimit);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferFunctions"); TEST_SUITE_BEGIN("TypeInferFunctions");
TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks") TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks")
@ -2129,11 +2131,21 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (DFFlag::LuauImproveNonFunctionCallError)
{
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
}
else
{
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }"); CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
else else
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}"); CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
} }
}
TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod") TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod")
{ {
@ -2535,4 +2547,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_definition_in_a_do_block_with_globa
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "fuzzer_alias_global_function_doesnt_hit_nil_assert")
{
CheckResult result = check(R"(
function _()
end
local function l0()
function _()
end
end
_ = _
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "fuzzer_bug_missing_follow_causes_assertion")
{
CheckResult result = check(R"(
local _ = ({_=function()
return _
end,}),true,_[_()]
for l0=_[_[_[`{function(l0)
end}`]]],_[_.n6[_[_.n6]]],_[_[_.n6[_[_.n6]]]] do
_ += if _ then ""
end
return _
)");
}
TEST_CASE_FIXTURE(Fixture, "cannot_call_union_of_functions")
{
CheckResult result = check(R"(
local f: (() -> ()) | (() -> () -> ()) = nil :: any
f()
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (DFFlag::LuauImproveNonFunctionCallError)
{
std::string expected = R"(Cannot call a value of the union type:
| () -> ()
| () -> () -> ()
We are unable to determine the appropriate result type for such a call.)";
CHECK(expected == toString(result.errors[0]));
}
else
CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0]));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -17,6 +17,8 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution) LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties) LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties)
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferLoops"); TEST_SUITE_BEGIN("TypeInferLoops");
TEST_CASE_FIXTURE(Fixture, "for_loop") TEST_CASE_FIXTURE(Fixture, "for_loop")
@ -165,6 +167,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (DFFlag::LuauImproveNonFunctionCallError)
CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
else
CHECK_EQ("Cannot call non-function string", toString(result.errors[0])); CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
} }

View File

@ -592,6 +592,9 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil :)
else
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
} }

View File

@ -22,6 +22,9 @@ LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering); LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
LUAU_FASTFLAG(DebugLuauSharedSelf); LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauReadWritePropertySyntax); LUAU_FASTFLAG(LuauReadWritePropertySyntax);
LUAU_FASTFLAG(LuauMetatableInstantiationCloneCheck);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -2383,6 +2386,10 @@ b()
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (DFFlag::LuauImproveNonFunctionCallError)
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
else
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })"); CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
} }
@ -3016,6 +3023,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
if (DFFlag::LuauImproveNonFunctionCallError)
CHECK("Cannot call a value of type { @metatable { __call: number }, { } }" == toString(result.errors[0]));
else
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0])); CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0]));
} }
else else
@ -3994,7 +4004,8 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string expected = "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number" std::string expected =
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
"\n\tat [read \"b\"], boolean is not exactly string" "\n\tat [read \"b\"], boolean is not exactly string"
"\n\tat [read \"c\"], number is not exactly boolean"; "\n\tat [read \"c\"], number is not exactly boolean";
CHECK(toString(result.errors[0]) == expected); CHECK(toString(result.errors[0]) == expected);
@ -4144,10 +4155,7 @@ TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported") TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
CheckResult result = check(R"( CheckResult result = check(R"(
type W = {read x: number} type W = {read x: number}
@ -4171,10 +4179,7 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported") TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
CheckResult result = check(R"( CheckResult result = check(R"(
type T = {read [string]: number} type T = {read [string]: number}
@ -4191,10 +4196,7 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
{ {
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, true}};
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
function oc(player, speaker) function oc(player, speaker)
@ -4359,4 +4361,47 @@ TEST_CASE_FIXTURE(Fixture, "setindexer_always_transmute")
CHECK_EQ("(*error-type*) -> ()", toString(requireType("f"))); CHECK_EQ("(*error-type*) -> ()", toString(requireType("f")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiated_metatable_frozen_table_clone_mutation")
{
ScopedFastFlag luauMetatableInstantiationCloneCheck{FFlag::LuauMetatableInstantiationCloneCheck, true};
fileResolver.source["game/worker"] = R"(
type WorkerImpl<T..., R...> = {
destroy: (self: Worker<T..., R...>) -> boolean,
}
type WorkerProps = { id: number }
export type Worker<T..., R...> = typeof(setmetatable({} :: WorkerProps, {} :: WorkerImpl<T..., R...>))
return {}
)";
fileResolver.source["game/library"] = R"(
local Worker = require(game.worker)
export type Worker<T..., R...> = Worker.Worker<T..., R...>
return {}
)";
CheckResult result = frontend.check("game/library");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection")
{
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
CheckResult result = check(R"(
local function f(t: { [string]: number } & { [thread]: boolean }, x)
local k = "a"
t[k] = x
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("({ [string]: number } & { [thread]: boolean }, boolean | number) -> ()" == toString(requireType("f")));
}
TEST_SUITE_END(); TEST_SUITE_END();

View File

@ -200,7 +200,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assignment_swap")
CHECK("number" == toString(requireType("b"))); CHECK("number" == toString(requireType("b")));
} }
TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types") TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types_2")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
local function f(x): number? local function f(x): number?

View File

@ -12,6 +12,25 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
TEST_SUITE_BEGIN("UnionTypes"); TEST_SUITE_BEGIN("UnionTypes");
TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion")
{
CheckResult result = check(R"(
local _ = {},nil
repeat
_,_ = if _.number == "" or _.number or _._ then
_
elseif _.__index == _._G then
tostring
elseif _ then
_
else
``,_._G
until _._
)");
}
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint") TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
@ -572,7 +591,8 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
UnionType u; UnionType u;
u.options.push_back(badCyclicUnionTy); u.options.push_back(badCyclicUnionTy);
u.options.push_back(arena.addType(TableType{{}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed})); u.options.push_back(arena.addType(TableType{
{}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed}));
asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u)); asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u));

View File

@ -64,6 +64,39 @@ do
a = table.move({[minI] = 100}, minI, minI, maxI) a = table.move({[minI] = 100}, minI, minI, maxI)
eqT(a, {[minI] = 100, [maxI] = 100}) eqT(a, {[minI] = 100, [maxI] = 100})
-- moving small amount of elements (array/hash) using a wide range
a = {}
table.move({1, 2, 3, 4, 5}, -100000000, 100000000, -100000000, a)
eqT(a, {1, 2, 3, 4, 5})
a = {}
table.move({1, 2}, -100000000, 100000000, 0, a)
eqT(a, {[100000001] = 1, [100000002] = 2})
-- hash part copy
a = {}
table.move({[-1000000] = 1, [-100] = 2, [100] = 3, [100000] = 4}, -100000000, 100000000, 0, a)
eqT(a, {[99000000] = 1, [99999900] = 2, [100000100] = 3, [100100000] = 4})
-- precise hash part bounds
a = {}
table.move({[-100000000 - 1] = -1, [-100000000] = 1, [-100] = 2, [100] = 3, [100000000] = 4, [100000000 + 1] = -1}, -100000000, 100000000, 0, a)
eqT(a, {[0] = 1, [99999900] = 2, [100000100] = 3, [200000000] = 4})
-- no integer undeflow in corner hash part case
a = {}
table.move({[minI] = 100, [-100] = 2}, minI, minI + 100000000, minI, a)
eqT(a, {[minI] = 100})
-- hash part skips array slice
a = {}
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4}, -1, 3, 1, a)
eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4})
a = {}
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4, [10] = 5, [100] = 6, [1000] = 7}, -1, 3, 1, a)
eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4})
end end
checkerror("too many", table.move, {}, 0, maxI, 1) checkerror("too many", table.move, {}, 0, maxI, 1)

View File

@ -49,8 +49,6 @@ GenericsTests.bound_tables_do_not_clone_original_fields
GenericsTests.correctly_instantiate_polymorphic_member_functions GenericsTests.correctly_instantiate_polymorphic_member_functions
GenericsTests.do_not_always_instantiate_generic_intersection_types GenericsTests.do_not_always_instantiate_generic_intersection_types
GenericsTests.do_not_infer_generic_functions GenericsTests.do_not_infer_generic_functions
GenericsTests.dont_leak_generic_types
GenericsTests.dont_leak_inferred_generic_types
GenericsTests.dont_substitute_bound_types GenericsTests.dont_substitute_bound_types
GenericsTests.error_detailed_function_mismatch_generic_pack GenericsTests.error_detailed_function_mismatch_generic_pack
GenericsTests.error_detailed_function_mismatch_generic_types GenericsTests.error_detailed_function_mismatch_generic_types
@ -155,7 +153,6 @@ RefinementTest.x_is_not_instance_or_else_not_part
TableTests.a_free_shape_can_turn_into_a_scalar_directly TableTests.a_free_shape_can_turn_into_a_scalar_directly
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
TableTests.accidentally_checked_prop_in_opposite_branch
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
TableTests.array_factory_function TableTests.array_factory_function
TableTests.casting_tables_with_props_into_table_with_indexer2 TableTests.casting_tables_with_props_into_table_with_indexer2
@ -395,6 +392,8 @@ TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_index TypeInferPrimitives.string_index
TypeInferUnknownNever.assign_to_local_which_is_never TypeInferUnknownNever.assign_to_local_which_is_never
TypeInferUnknownNever.compare_never
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypeInferUnknownNever.length_of_never TypeInferUnknownNever.length_of_never
@ -412,7 +411,6 @@ TypeSingletons.overloaded_function_call_with_singletons_mismatch
TypeSingletons.return_type_of_f_is_not_widened TypeSingletons.return_type_of_f_is_not_widened
TypeSingletons.table_properties_type_error_escapes TypeSingletons.table_properties_type_error_escapes
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments
TypeStatesTest.typestates_preserve_error_suppression_properties TypeStatesTest.typestates_preserve_error_suppression_properties
UnionTypes.error_detailed_optional UnionTypes.error_detailed_optional
UnionTypes.error_detailed_union_all UnionTypes.error_detailed_union_all