Sync to upstream/release/622

This commit is contained in:
Aaron Weiss 2024-04-19 14:04:30 -07:00
parent 0f0c0e4d28
commit 67b9145268
61 changed files with 1405 additions and 753 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(
NotNull{&kBuiltinTypeFamilies.refineFamily}, TypeFamilyInstanceType{
{ty, dt}, NotNull{&kBuiltinTypeFamilies.refineFamily},
{}, {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)));
} }
@ -1162,7 +1173,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
if (typeState) if (typeState)
{ {
NotNull<Constraint> uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/ true}); NotNull<Constraint> uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/true});
if (auto blocked = getMutable<BlockedType>(*typeState); blocked && !blocked->getOwner()) if (auto blocked = getMutable<BlockedType>(*typeState); blocked && !blocked->getOwner())
blocked->setOwner(uc); blocked->setOwner(uc);
@ -1178,7 +1189,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement)
{ {
RefinementId refinement = [&](){ RefinementId refinement = [&]() {
InConditionalContext flipper{&typeContext}; InConditionalContext flipper{&typeContext};
return check(scope, ifStatement->condition, std::nullopt).refinement; return check(scope, ifStatement->condition, std::nullopt).refinement;
}(); }();
@ -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(
NotNull{&kBuiltinTypeFamilies.notFamily}, TypeFamilyInstanceType{
{operandType}, NotNull{&kBuiltinTypeFamilies.notFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.lenFamily}, TypeFamilyInstanceType{
{operandType}, NotNull{&kBuiltinTypeFamilies.lenFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.unmFamily}, TypeFamilyInstanceType{
{operandType}, NotNull{&kBuiltinTypeFamilies.unmFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.addFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.addFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.subFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.subFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.mulFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.mulFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.divFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.divFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.idivFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.idivFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.powFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.powFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.modFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.modFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.concatFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.concatFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.andFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.andFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.orFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.orFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.ltFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.ltFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.ltFamily}, TypeFamilyInstanceType{
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` NotNull{&kBuiltinTypeFamilies.ltFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.leFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.leFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.leFamily}, TypeFamilyInstanceType{
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` NotNull{&kBuiltinTypeFamilies.leFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.eqFamily}, TypeFamilyInstanceType{
{leftType, rightType}, NotNull{&kBuiltinTypeFamilies.eqFamily},
{}, {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:
@ -2173,7 +2222,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType) Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
{ {
RefinementId refinement = [&](){ RefinementId refinement = [&]() {
InConditionalContext flipper{&typeContext}; InConditionalContext flipper{&typeContext};
ScopePtr condScope = childScope(ifElse->condition, scope); ScopePtr condScope = childScope(ifElse->condition, scope);
return check(condScope, ifElse->condition).refinement; return check(condScope, ifElse->condition).refinement;
@ -2612,14 +2661,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
LUAU_ASSERT(!indexValueLowerBound.empty()); LUAU_ASSERT(!indexValueLowerBound.empty());
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(
NotNull{&kBuiltinTypeFamilies.unionFamily}, TypeFamilyInstanceType{
{lhs, rhs}, NotNull{&kBuiltinTypeFamilies.unionFamily},
{}, {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(
NotNull{&kBuiltinTypeFamilies.intersectFamily}, TypeFamilyInstanceType{
{lhs, rhs}, NotNull{&kBuiltinTypeFamilies.intersectFamily},
{}, {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);
@ -1538,12 +1496,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
// ~(false | nil) <: {[indexType]: resultType} // ~(false | nil) <: {[indexType]: resultType}
Set<TypeId> parts{nullptr}; Set<TypeId> parts{nullptr};
for (TypeId part: it) for (TypeId part : it)
parts.insert(follow(part)); parts.insert(follow(part));
Set<TypeId> results{nullptr}; Set<TypeId> results{nullptr};
for (TypeId part: parts) for (TypeId part : parts)
{ {
TypeId r = arena->addType(BlockedType{}); TypeId r = arena->addType(BlockedType{});
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get())); getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
@ -1570,12 +1528,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
else if (auto ut = get<UnionType>(subjectType)) else if (auto ut = get<UnionType>(subjectType))
{ {
Set<TypeId> parts{nullptr}; Set<TypeId> parts{nullptr};
for (TypeId part: ut) for (TypeId part : ut)
parts.insert(follow(part)); parts.insert(follow(part));
Set<TypeId> results{nullptr}; Set<TypeId> results{nullptr};
for (TypeId part: parts) for (TypeId part : parts)
{ {
TypeId r = arena->addType(BlockedType{}); TypeId r = arena->addType(BlockedType{});
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get())); getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
@ -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);
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
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);
}
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)
@ -2075,7 +2012,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
LUAU_ASSERT(nextFn); LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes; const TypePackId nextRetPack = nextFn->retTypes;
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/ true}); pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true});
return true; return true;
} }
else else
@ -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
{ {
@ -910,13 +917,13 @@ static bool isCacheable(TypeId ty, Set<TypeId>& seen)
if (auto tfi = get<TypeFamilyInstanceType>(ty)) if (auto tfi = get<TypeFamilyInstanceType>(ty))
{ {
for (TypeId t: tfi->typeArguments) for (TypeId t : tfi->typeArguments)
{ {
if (!isCacheable(t, seen)) if (!isCacheable(t, seen))
return false; return false;
} }
for (TypePackId tp: tfi->packArguments) for (TypePackId tp : tfi->packArguments)
{ {
if (!isCacheable(tp, seen)) if (!isCacheable(tp, seen))
return false; return false;
@ -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,41 +365,25 @@ 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);
if (status == OverloadResolver::Analysis::Ok) if (status == OverloadResolver::Analysis::Ok)
return overload; return overload;
if (get<AnyType>(fn) || get<FreeType>(fn)) if (get<AnyType>(fn) || get<FreeType>(fn))
return fn; return fn;
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)
@ -1244,18 +1245,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{ {
if (superProp.isShared()) if (superProp.isShared())
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type()) results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
.withSubComponent(TypePath::TypeField::IndexResult) .withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name))); .withSuperComponent(TypePath::Property::read(name)));
else else
{ {
if (superProp.readTy) if (superProp.readTy)
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy) results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
.withSubComponent(TypePath::TypeField::IndexResult) .withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name))); .withSuperComponent(TypePath::Property::read(name)));
if (superProp.writeTy) if (superProp.writeTy)
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy) results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
.withSubComponent(TypePath::TypeField::IndexResult) .withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::write(name))); .withSuperComponent(TypePath::Property::write(name)));
} }
} }
} }
@ -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
@ -52,7 +46,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
size_t tableCount = 0; size_t tableCount = 0;
std::optional<TypeId> firstTable; std::optional<TypeId> firstTable;
for (TypeId ty: tables) for (TypeId ty : tables)
{ {
ty = follow(ty); ty = follow(ty);
if (auto tt = get<TableType>(ty)) if (auto tt = get<TableType>(ty))
@ -65,7 +59,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
firstTable = ty; firstTable = ty;
++tableCount; ++tableCount;
for (const auto& [name, expectedProp]: tt->props) for (const auto& [name, expectedProp] : tt->props)
{ {
if (!expectedProp.readTy) if (!expectedProp.readTy)
continue; continue;
@ -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);
@ -247,7 +233,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
return exprType; return exprType;
} }
for (const AstExprTable::Item& item: exprTable->items) for (const AstExprTable::Item& item : exprTable->items)
{ {
if (isRecord(item)) if (isRecord(item))
{ {
@ -391,7 +377,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
for (const auto& [name, _] : expectedTableTy->props) for (const auto& [name, _] : expectedTableTy->props)
missingKeys.insert(name); missingKeys.insert(name);
for (const AstExprTable::Item& item: exprTable->items) for (const AstExprTable::Item& item : exprTable->items)
{ {
if (item.key) if (item.key)
{ {
@ -402,7 +388,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
} }
} }
for (const auto& key: missingKeys) for (const auto& key : missingKeys)
{ {
LUAU_ASSERT(key.has_value()); LUAU_ASSERT(key.has_value());
@ -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>)
@ -1923,7 +1890,7 @@ std::string toString(const Position& position)
std::string toString(const Location& location, int offset, bool useBegin) std::string toString(const Location& location, int offset, bool useBegin)
{ {
return "(" + std::to_string(location.begin.line + offset) + ", " + std::to_string(location.begin.column + offset) + ") - (" + return "(" + std::to_string(location.begin.line + offset) + ", " + std::to_string(location.begin.column + offset) + ") - (" +
std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")"; std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")";
} }
std::string toString(const TypeOrPack& tyOrTp, ToStringOptions& opts) std::string toString(const TypeOrPack& tyOrTp, ToStringOptions& opts)

View file

@ -546,13 +546,15 @@ 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)
return; return;

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);
@ -243,7 +244,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible) if (skip == SkipTestResult::Irreducible)
{ {
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
irreducible.insert(subject); irreducible.insert(subject);
return false; return false;
@ -251,7 +252,7 @@ struct FamilyReducer
else if (skip == SkipTestResult::Defer) else if (skip == SkipTestResult::Defer)
{ {
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
if constexpr (std::is_same_v<T, TypeId>) if constexpr (std::is_same_v<T, TypeId>)
queuedTys.push_back(subject); queuedTys.push_back(subject);
@ -269,7 +270,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible) if (skip == SkipTestResult::Irreducible)
{ {
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
irreducible.insert(subject); irreducible.insert(subject);
return false; return false;
@ -277,7 +278,7 @@ struct FamilyReducer
else if (skip == SkipTestResult::Defer) else if (skip == SkipTestResult::Defer)
{ {
if (FFlag::DebugLuauLogSolver) if (FFlag::DebugLuauLogSolver)
printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str()); printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
if constexpr (std::is_same_v<T, TypeId>) if constexpr (std::is_same_v<T, TypeId>)
queuedTys.push_back(subject); queuedTys.push_back(subject);
@ -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,25 +738,18 @@ 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)
return {results[0], false, {}, {}};
else if (results.size() == 2)
{
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(results),
{},
});
queue->add(resultTy); if (results.size() == 1)
return {resultTy, false, {}, {}}; return {results[0], false, {}, {}};
}
else TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
{ NotNull{&kBuiltinTypeFamilies.unionFamily},
// TODO: We need to generalize `union<...>` type family to be variadic. std::move(results),
TypeId resultTy = ctx->arena->addType(UnionType{std::move(results)}); {},
return {resultTy, false, {}, {}}; });
}
queue->add(resultTy);
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
@ -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))
if (!result.blockedTypes.empty()) continue;
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; lastType = ty;
}
return {result.result, false, {}, {}}; // if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
if (lastType)
return {lastType, false, {}, {}};
// check to see if the operand types are resolved enough, and wait to reduce if not
for (auto ty : types)
if (isPending(ty, ctx->solver))
return {std::nullopt, false, {ty}, {}};
// fold over the types with `simplifyUnion`
TypeId resultTy = ctx->builtins->neverType;
for (auto ty : types)
{
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
resultTy = result.result;
}
return {resultTy, false, {}, {}};
} }
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 (!result.blockedTypes.empty()) // if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; 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())
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;
@ -52,7 +50,7 @@ static bool areCompatible(TypeId left, TypeId right)
return false; return false;
}; };
for (const auto& [name, leftProp]: leftTable->props) for (const auto& [name, leftProp] : leftTable->props)
{ {
auto it = rightTable->props.find(name); auto it = rightTable->props.find(name);
if (it == rightTable->props.end()) if (it == rightTable->props.end())
@ -62,7 +60,7 @@ static bool areCompatible(TypeId left, TypeId right)
} }
} }
for (const auto& [name, rightProp]: rightTable->props) for (const auto& [name, rightProp] : rightTable->props)
{ {
auto it = leftTable->props.find(name); auto it = leftTable->props.find(name);
if (it == leftTable->props.end()) if (it == leftTable->props.end())
@ -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

@ -121,7 +121,7 @@ ExtraExecData* getExtraExecData(Proto* proto, void* execdata)
static OldNativeProto createOldNativeProto(Proto* proto, const IrBuilder& ir) static OldNativeProto createOldNativeProto(Proto* proto, const IrBuilder& ir)
{ {
CODEGEN_ASSERT(!FFlag::LuauCodegenContext); CODEGEN_ASSERT(!FFlag::LuauCodegenContext);
int execDataSize = calculateExecDataSize(proto); int execDataSize = calculateExecDataSize(proto);
CODEGEN_ASSERT(execDataSize % 4 == 0); CODEGEN_ASSERT(execDataSize % 4 == 0);

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

@ -11,9 +11,9 @@ inline bool isFlagExperimental(const char* flag)
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final, // Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
// or critical bugs that are found after the code has been submitted. // or critical bugs that are found after the code has been submitted.
static const char* const kList[] = { static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins "LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,
}; };

View file

@ -1,6 +1,7 @@
MIT License MIT License
Copyright (c) 2019-2023 Roblox Corporation Copyright (c) 2019-2024 Roblox Corporation
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
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in
@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

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,36 +145,38 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
{ {
}; };
#define LUAU_CHECK_HAS_KEY(map, key) do \ #define LUAU_CHECK_HAS_KEY(map, key) \
{ \ do \
auto&& _m = (map); \ { \
auto&& _k = (key); \ auto&& _m = (map); \
const size_t count = _m.count(_k); \ auto&& _k = (key); \
const size_t count = _m.count(_k); \
CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \ CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
if (!count) \ if (!count) \
{ \ { \
MESSAGE("Keys: (count " << _m.size() << ")"); \ MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v]: _m) \ for (const auto& [k, v] : _m) \
{ \ { \
MESSAGE("\tkey: " << k); \ MESSAGE("\tkey: " << k); \
} \ } \
} \ } \
} 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&& _k = (key); \ auto&& _m = (map); \
const size_t count = _m.count(_k); \ auto&& _k = (key); \
const size_t count = _m.count(_k); \
CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \ CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
if (count) \ if (count) \
{ \ { \
MESSAGE("Keys: (count " << _m.size() << ")"); \ MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v]: _m) \ for (const auto& [k, v] : _m) \
{ \ { \
MESSAGE("\tkey: " << k); \ MESSAGE("\tkey: " << k); \
} \ } \
} \ } \
} while (false) } while (false)
TEST_SUITE_BEGIN("AutocompleteTest"); TEST_SUITE_BEGIN("AutocompleteTest");

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");
} }
@ -1837,7 +1840,7 @@ TEST_CASE("DebugApi")
lua_pushnumber(L, 10); lua_pushnumber(L, 10);
lua_Debug ar; lua_Debug ar;
CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function
CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack
} }
@ -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)
{ {
@ -402,7 +403,7 @@ local function vecrcp(a: vector)
return vector(1, 2, 3) + a return vector(1, 2, 3) + a
end end
)"), )"),
R"( R"(
; function vecrcp($arg0) line 2 ; function vecrcp($arg0) line 2
bb_0: bb_0:
CHECK_TAG R0, tvector, exit(entry) CHECK_TAG R0, tvector, exit(entry)
@ -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

@ -225,9 +225,9 @@ struct SubtypeFixture : Fixture
}); });
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", { TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
{"X", Property::readonly(builtinTypes->numberType)}, {"X", Property::readonly(builtinTypes->numberType)},
{"Y", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)},
}); });
// "hello" | "hello" // "hello" | "hello"
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}}); TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
@ -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)}, instanceType, nullopt, {}, {}, "Test"});
{"Parent", Property::rw(workspaceType, instanceType)}
},
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,10 +2131,20 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::DebugLuauDeferredConstraintResolution) if (DFFlag::LuauImproveNonFunctionCallError)
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }"); {
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 else
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}"); {
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
else
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,7 +167,11 @@ 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);
CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
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]));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")

View file

@ -338,7 +338,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_prop")
)"); )");
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
else else
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result); LUAU_REQUIRE_ERROR_COUNT(2, result);
@ -592,7 +592,10 @@ 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
CHECK_EQ(toString(requireTypeAtPosition({5, 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
} }
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")

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,7 +2386,11 @@ b()
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
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 }, { } })");
} }
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables") TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
@ -3016,7 +3023,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
if (FFlag::DebugLuauDeferredConstraintResolution) if (FFlag::DebugLuauDeferredConstraintResolution)
{ {
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0])); 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]));
} }
else else
{ {
@ -3994,9 +4004,10 @@ 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 =
"\n\tat [read \"b\"], boolean is not exactly string" "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
"\n\tat [read \"c\"], number is not exactly boolean"; "\n\tat [read \"b\"], boolean is not exactly string"
"\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)
@ -4206,8 +4208,8 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK("<a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () " CHECK("<a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () "
"where " "where "
"t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc"))); "t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers") TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers")
@ -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

@ -986,7 +986,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_found_this")
*/ */
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
{ {
(void) check(R"( (void)check(R"(
local _ local _
if _ then if _ then
_ = _ _ = _
@ -999,7 +999,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
TEST_CASE_FIXTURE(Fixture, "indexing_a_cyclic_intersection_does_not_crash") TEST_CASE_FIXTURE(Fixture, "indexing_a_cyclic_intersection_does_not_crash")
{ {
(void) check(R"( (void)check(R"(
local _ local _
if _ then if _ then
while nil do while nil do

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