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;
};
// subType ~ inst superType
struct InstantiationConstraint
{
TypeId subType;
TypeId superType;
};
// variables ~ iterate iterator
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
struct IterableConstraint
@ -229,17 +222,6 @@ struct SetIndexerConstraint
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
//
// Similar to PackSubtypeConstraint, but with one important difference: If the
@ -269,22 +251,6 @@ struct Unpack1Constraint
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
//
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
@ -301,10 +267,9 @@ struct ReducePackConstraint
TypePackId tp;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint,
SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, IterableConstraint, NameConstraint,
TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
HasIndexerConstraint, SetIndexerConstraint, UnpackConstraint, Unpack1Constraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
struct Constraint
{

View file

@ -91,6 +91,9 @@ struct ConstraintSolver
// A mapping from free types to the number of unresolved constraints that mention them.
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.
ErrorVec errors;
@ -124,7 +127,6 @@ struct ConstraintSolver
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 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 NameConstraint& 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 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);
/// (dispatched, found) where
/// - dispatched: this constraint can be considered having dispatched.
/// - 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);
std::pair<bool, std::optional<TypeId>> 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 SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
bool tryDispatch(const UnpackConstraint& 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 ReducePackConstraint& 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
{
enum OverloadCallResult {
enum OverloadCallResult
{
Ok,
CodeTooComplex,
OccursCheckFailed,
@ -87,16 +88,8 @@ struct SolveResult
// Helper utility, presently used for binary operator type families.
//
// Given a function and a set of arguments, select a suitable overload.
SolveResult solveFunctionCall(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope,
const Location& location,
TypeId fn,
TypePackId argsPack
);
SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, const Location& location, TypeId fn,
TypePackId argsPack);
} // namespace Luau

View file

@ -208,7 +208,8 @@ private:
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 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 TypeIds& superTables);
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,
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Unifier2> unifier, TypeId expectedType, TypeId exprType,
const AstExpr* expr, std::vector<TypeId>& toBlock);
}
} // namespace Luau

View file

@ -552,6 +552,27 @@ struct TypeFamilyInstanceType
std::vector<TypeId> typeArguments;
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.

View file

@ -48,6 +48,11 @@ struct TypeArena
{
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);

View file

@ -99,8 +99,8 @@ struct TypeFamilyReductionResult
};
template<typename T>
using ReducerFunction =
std::function<TypeFamilyReductionResult<T>(T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
using ReducerFunction = std::function<TypeFamilyReductionResult<T>(
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
/// type packs to a single output type.
@ -189,6 +189,7 @@ struct BuiltinTypeFamilies
TypeFamily eqFamily;
TypeFamily refineFamily;
TypeFamily singletonFamily;
TypeFamily unionFamily;
TypeFamily intersectFamily;

View file

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

View file

@ -79,9 +79,18 @@ enum class PackField
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
/// 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
/// contained within.
@ -156,6 +165,7 @@ struct PathHash
size_t operator()(const Index& idx) const;
size_t operator()(const TypeField& 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 Path& path) const;
};

View file

@ -48,8 +48,12 @@ struct Unifier2
int recursionLimit = 0;
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,
DenseHashSet<const void*>* uninhabitedTypeFamilies);
/** Attempt to commit the subtype relation subTy <: superTy to the type
* graph.

View file

@ -24,7 +24,6 @@
*/
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauSetMetatableOnUnionsOfTables, false);
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
namespace Luau
@ -1067,7 +1066,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
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);
LUAU_ASSERT(ut);

View file

@ -233,7 +233,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
Checkpoint end = checkpoint(this);
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);
forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
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))
{
TypeId discriminantTy = proposition->discriminantTy;
if (!sense && !eq)
discriminantTy = arena->addType(NegationType{proposition->discriminantTy});
else if (eq)
{
discriminantTy = arena->addType(BlockedType{});
constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy, !sense});
}
// if we have a negative sense, then we need to negate the discriminant
if (!sense)
discriminantTy = arena->addType(NegationType{discriminantTy});
if (eq)
discriminantTy = arena->addTypeFamily(kBuiltinTypeFamilies.singletonFamily, {discriminantTy});
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))
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
}, scope, location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
},
scope, location);
ty = resultType;
}
@ -961,7 +964,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
// With or without self
TypeId generalizedType = arena->addType(BlockedType{});
Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
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)));
}
@ -1162,7 +1173,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
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())
blocked->setOwner(uc);
@ -1178,7 +1189,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement)
{
RefinementId refinement = [&](){
RefinementId refinement = [&]() {
InConditionalContext flipper{&typeContext};
return check(scope, ifStatement->condition, std::nullopt).refinement;
}();
@ -1708,8 +1719,8 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
* 4. Solve the call
*/
NotNull<Constraint> checkConstraint =
addConstraint(scope, call->func->location, FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}});
NotNull<Constraint> checkConstraint = addConstraint(scope, call->func->location,
FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}});
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) {
checkConstraint->dependencies.emplace_back(constraint.get());
@ -1901,7 +1912,8 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
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);
if (key)
@ -1957,7 +1969,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint endCheckpoint = checkpoint(this);
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);
interiorTypes.pop_back();
@ -1992,29 +2005,35 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
{
case AstExprUnary::Op::Not:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.notFamily},
{operandType},
{},
}, scope, unary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.notFamily},
{operandType},
{},
},
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
case AstExprUnary::Op::Len:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.lenFamily},
{operandType},
{},
}, scope, unary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.lenFamily},
{operandType},
{},
},
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
case AstExprUnary::Op::Minus:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unmFamily},
{operandType},
{},
}, scope, unary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unmFamily},
{operandType},
{},
},
scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
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:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.addFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.addFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Sub:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.subFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.subFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Mul:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.mulFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.mulFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Div:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.divFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.divFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::FloorDiv:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.idivFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.idivFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Pow:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.powFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.powFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Mod:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.modFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.modFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Concat:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.concatFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.concatFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::And:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.andFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.andFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Or:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.orFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.orFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareLt:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareGe:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareLe:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareGt:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareEq:
case AstExprBinary::Op::CompareNe:
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.eqFamily},
{leftType, rightType},
{},
}, scope, binary->location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.eqFamily},
{leftType, rightType},
{},
},
scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
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)
{
RefinementId refinement = [&](){
RefinementId refinement = [&]() {
InConditionalContext flipper{&typeContext};
ScopePtr condScope = childScope(ifElse->condition, scope);
return check(condScope, ifElse->condition).refinement;
@ -2612,14 +2661,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
LUAU_ASSERT(!indexValueLowerBound.empty());
TypeId indexKey = indexKeyLowerBound.size() == 1
? *indexKeyLowerBound.begin()
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())})
;
? *indexKeyLowerBound.begin()
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
TypeId indexValue = indexValueLowerBound.size() == 1
? *indexValueLowerBound.begin()
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())})
;
? *indexValueLowerBound.begin()
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
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 resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
{lhs, rhs},
{},
}, scope, location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
{lhs, rhs},
{},
},
scope, location);
return resultType;
}
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.intersectFamily},
{lhs, rhs},
{},
}, scope, location);
TypeId resultType = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.intersectFamily},
{lhs, rhs},
{},
},
scope, location);
return resultType;
}
@ -3329,9 +3380,13 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
scope->bindings[symbol] = Binding{tys.front(), location};
else
{
TypeId ty = arena->addType(BlockedType{});
auto c = addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});
getMutable<BlockedType>(ty)->setOwner(c);
TypeId ty = createFamilyInstance(
TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(tys),
{},
},
globalScope, Location{});
scope->bindings[symbol] = Binding{ty, location};
}

View file

@ -346,7 +346,8 @@ void ConstraintSolver::run()
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);
printf("Bindings:\n");
dumpBindings(rootScope, opts);
@ -492,8 +493,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*psc, constraint, force);
else if (auto gc = get<GeneralizationConstraint>(*constraint))
success = tryDispatch(*gc, constraint, force);
else if (auto ic = get<InstantiationConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto ic = get<IterableConstraint>(*constraint))
success = tryDispatch(*ic, constraint, force);
else if (auto nc = get<NameConstraint>(*constraint))
@ -514,14 +513,10 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*spc, constraint);
else if (auto spc = get<SetIndexerConstraint>(*constraint))
success = tryDispatch(*spc, constraint, force);
else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint))
success = tryDispatch(*sottc, constraint);
else if (auto uc = get<UnpackConstraint>(*constraint))
success = tryDispatch(*uc, constraint);
else if (auto uc = get<Unpack1Constraint>(*constraint))
success = tryDispatch(*uc, constraint);
else if (auto soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force);
else if (auto rc = get<ReduceConstraint>(*constraint))
success = tryDispatch(*rc, constraint, force);
else if (auto rpc = get<ReducePackConstraint>(*constraint))
@ -611,40 +606,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
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)
{
/*
@ -936,7 +897,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
// Type function application will happily give us the exact same type if
// 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.
TableType* ttv = getMutableTableType(target);
@ -1462,7 +1424,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
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};
@ -1481,12 +1444,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
asMutable(resultType)->ty.emplace<FreeType>(freeResult);
TypeId upperBound = arena->addType(TableType{
/* props */ {},
TableIndexer{indexType, resultType},
TypeLevel{},
TableState::Unsealed
});
TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, TableState::Unsealed});
unify(constraint, subjectType, upperBound);
@ -1538,12 +1496,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
// ~(false | nil) <: {[indexType]: resultType}
Set<TypeId> parts{nullptr};
for (TypeId part: it)
for (TypeId part : it)
parts.insert(follow(part));
Set<TypeId> results{nullptr};
for (TypeId part: parts)
for (TypeId part : parts)
{
TypeId r = arena->addType(BlockedType{});
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))
{
Set<TypeId> parts{nullptr};
for (TypeId part: ut)
for (TypeId part : ut)
parts.insert(follow(part));
Set<TypeId> results{nullptr};
for (TypeId part: parts)
for (TypeId part : parts)
{
TypeId r = arena->addType(BlockedType{});
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)
{
@ -1651,25 +1609,24 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const
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))
return {block(subjectType, constraint), false};
return {block(subjectType, constraint), std::nullopt};
if (auto tt = getMutable<TableType>(subjectType))
{
if (tt->indexer)
{
unify(constraint, indexType, tt->indexer->indexType);
bindBlockedType(propType, tt->indexer->indexResultType, subjectType, constraint);
return {true, true};
return {true, tt->indexer->indexResultType};
}
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
{
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
tt->indexer = TableIndexer{indexType, propType};
return {true, true};
TypeId resultTy = freshType(arena, builtinTypes, constraint->scope.get());
tt->indexer = TableIndexer{indexType, resultTy};
return {true, resultTy};
}
}
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.
//
// 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);
if (dispatched && !found)
auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/false);
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.
found = true;
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
resultTy = freshType(arena, builtinTypes, constraint->scope.get());
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
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))
{
std::pair<bool, bool> result{true, true};
bool dispatched = true;
std::vector<TypeId> results;
for (TypeId part : it)
{
auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
result.first &= dispatched;
result.second &= found;
auto [dispatched2, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
dispatched &= dispatched2;
results.push_back(found.value_or(builtinTypes->errorRecoveryType()));
if (!dispatched)
return {dispatched, std::nullopt};
}
return result;
}
else if (is<AnyType, ErrorType, NeverType>(subjectType) && expandFreeTypeBounds)
{
bindBlockedType(propType, subjectType, subjectType, constraint);
return {true, true};
}
TypeId resultTy = arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(results),
{},
});
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)
@ -1721,54 +1688,38 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
if (isBlocked(subjectType))
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 (!found)
bindBlockedType(c.propType, builtinTypes->errorRecoveryType(), subjectType, constraint);
bindBlockedType(c.propType, resultTy.value_or(builtinTypes->errorRecoveryType()), subjectType, constraint);
unblock(c.propType, constraint->location);
}
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)
{
resultTy = follow(resultTy);
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;
LUAU_ASSERT(lt->blockCount > 0);
--lt->blockCount;
LUAU_ASSERT(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))
{
if (follow(srcTy) == resultTy)
@ -1823,21 +1774,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
TypeId srcTy = follow(srcPack.head[i]);
TypeId resultTy = follow(*resultIter);
if (resultTy)
{
// when we preserve the error-suppression of types through typestate,
// we introduce a union with the error type, so we need to find the local type in those options to update.
if (auto ut = getMutable<UnionType>(resultTy))
{
for (auto opt : ut->options)
tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue);
}
else
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
}
else
unify(constraint, srcTy, resultTy);
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
++resultIter;
++i;
@ -1877,34 +1814,6 @@ bool ConstraintSolver::tryDispatch(const Unpack1Constraint& c, NotNull<const Con
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)
{
TypeId ty = follow(c.ty);
@ -1917,6 +1826,20 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks)
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)
return true;
@ -1926,7 +1849,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId b : result.blockedPacks)
block(b, constraint);
return result.blockedTypes.empty() && result.blockedPacks.empty();
return reductionFinished;
}
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)
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)
return true;
@ -1950,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
for (TypePackId b : result.blockedPacks)
block(b, constraint);
return result.blockedTypes.empty() && result.blockedPacks.empty();
return reductionFinished;
}
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);
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;
}
else
@ -2405,7 +2342,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
template<typename TID>
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);
@ -2672,12 +2609,20 @@ bool ConstraintSolver::isBlocked(TypeId ty)
if (auto lt = get<LocalType>(ty))
return lt->blockCount > 0;
if (auto tfit = get<TypeFamilyInstanceType>(ty))
return uninhabitedTypeFamilies.contains(ty) == false;
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
}
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)

View file

@ -15,6 +15,8 @@
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
static std::string wrongNumberOfArgsString(
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;
}
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
{
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);
}
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.
// we default to the lower bound which represents the most specific type for the free type.
TypeId res = get<NeverType>(ft->lowerBound)
? ft->upperBound
: ft->lowerBound;
TypeId res = get<NeverType>(ft->lowerBound) ? ft->upperBound : ft->lowerBound;
// Instantiation should not traverse into the type that we are substituting for.
dontTraverseInto(res);

View file

@ -18,6 +18,8 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
LUAU_FASTFLAGVARIABLE(LuauFixCyclicUnionsOfIntersections, false);
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
@ -29,6 +31,11 @@ static bool fixNormalizeCaching()
return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution;
}
static bool fixCyclicUnionsOfIntersections()
{
return FFlag::LuauFixCyclicUnionsOfIntersections || FFlag::DebugLuauDeferredConstraintResolution;
}
namespace Luau
{
@ -910,13 +917,13 @@ static bool isCacheable(TypeId ty, Set<TypeId>& seen)
if (auto tfi = get<TypeFamilyInstanceType>(ty))
{
for (TypeId t: tfi->typeArguments)
for (TypeId t : tfi->typeArguments)
{
if (!isCacheable(t, seen))
return false;
}
for (TypePackId tp: tfi->packArguments)
for (TypePackId tp : tfi->packArguments)
{
if (!isCacheable(tp, seen))
return false;
@ -1768,14 +1775,29 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t
}
else if (const IntersectionType* itv = get<IntersectionType>(there))
{
if (fixCyclicUnionsOfIntersections())
{
if (seenSetTypes.count(there))
return NormalizationResult::True;
seenSetTypes.insert(there);
}
NormalizedType norm{builtinTypes};
norm.tops = builtinTypes->anyType;
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
{
NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes);
if (res != NormalizationResult::True)
{
if (fixCyclicUnionsOfIntersections())
seenSetTypes.erase(there);
return res;
}
}
if (fixCyclicUnionsOfIntersections())
seenSetTypes.erase(there);
return unionNormals(here, norm);
}
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.
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))
return intersectNormalWithTy(here, nt->ty, seenSetTypes);
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`.
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
std::optional<TypeId> selectOverload(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Normalizer> normalizer,
NotNull<Scope> scope,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
const Location& location,
TypeId fn,
TypePackId argsPack
)
std::optional<TypeId> selectOverload(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, 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};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
if (status == OverloadResolver::Analysis::Ok)
return overload;
if (status == OverloadResolver::Analysis::Ok)
return overload;
if (get<AnyType>(fn) || get<FreeType>(fn))
return fn;
if (get<AnyType>(fn) || get<FreeType>(fn))
return fn;
return {};
return {};
}
SolveResult solveFunctionCall(
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Normalizer> normalizer,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
NotNull<Scope> scope,
const Location& location,
TypeId fn,
TypePackId argsPack
)
SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, 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);
if (!overloadToUse)

View file

@ -266,7 +266,8 @@ struct ApplyMappedGenerics : Substitution
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)
, builtinTypes(builtinTypes)
, arena(arena)
@ -1244,18 +1245,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{
if (superProp.isShared())
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
else
{
if (superProp.readTy)
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::read(name)));
if (superProp.writeTy)
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::write(name)));
.withSubComponent(TypePath::TypeField::IndexResult)
.withSuperComponent(TypePath::Property::write(name)));
}
}
}
@ -1310,7 +1311,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
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};
@ -1421,7 +1423,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop
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)
return {false, true};
@ -1584,15 +1587,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
{
// Reduce the typefamily instance
auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance);
// 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)
{
// Reduce the typefamily instance
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)
{
return (
expr->is<AstExprTable>() ||
expr->is<AstExprFunction>() ||
expr->is<AstExprConstantNumber>() ||
expr->is<AstExprConstantString>() ||
expr->is<AstExprConstantBool>() ||
expr->is<AstExprConstantNil>()
);
return (expr->is<AstExprTable>() || expr->is<AstExprFunction>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantString>() ||
expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNil>());
}
// A fast approximation of subTy <: superTy
@ -52,7 +46,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
size_t tableCount = 0;
std::optional<TypeId> firstTable;
for (TypeId ty: tables)
for (TypeId ty : tables)
{
ty = follow(ty);
if (auto tt = get<TableType>(ty))
@ -65,7 +59,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
firstTable = ty;
++tableCount;
for (const auto& [name, expectedProp]: tt->props)
for (const auto& [name, expectedProp] : tt->props)
{
if (!expectedProp.readTy)
continue;
@ -91,14 +85,12 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
if (ft && get<SingletonType>(ft->lowerBound))
{
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
fastIsSubtype(expectedType, builtinTypes->booleanType))
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType))
{
return ty;
}
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
fastIsSubtype(expectedType, ft->lowerBound))
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound))
{
return ty;
}
@ -149,11 +141,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
if (expr->is<AstExprConstantString>())
{
auto ft = get<FreeType>(exprType);
if (ft &&
get<SingletonType>(ft->lowerBound) &&
fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
fastIsSubtype(ft->lowerBound, builtinTypes->stringType)
)
if (ft && get<SingletonType>(ft->lowerBound) && 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
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
@ -177,11 +166,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
else if (expr->is<AstExprConstantBool>())
{
auto ft = get<FreeType>(exprType);
if (ft &&
get<SingletonType>(ft->lowerBound) &&
fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)
)
if (ft && get<SingletonType>(ft->lowerBound) && 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
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
@ -247,7 +233,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
return exprType;
}
for (const AstExprTable::Item& item: exprTable->items)
for (const AstExprTable::Item& item : exprTable->items)
{
if (isRecord(item))
{
@ -391,7 +377,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
for (const auto& [name, _] : expectedTableTy->props)
missingKeys.insert(name);
for (const AstExprTable::Item& item: exprTable->items)
for (const AstExprTable::Item& item : exprTable->items)
{
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());
@ -427,4 +413,4 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
return exprType;
}
}
} // namespace Luau

View file

@ -1767,12 +1767,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
std::string superStr = tos(c.sourceType);
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>)
{
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);
}
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>)
return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack);
else if constexpr (std::is_same_v<T, Unpack1Constraint>)
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>)
return "reduce " + tos(c.ty);
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)
{
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)

View file

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

View file

@ -94,6 +94,26 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
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)
{
if (!FFlag::DebugLuauFreezeArena)

View file

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

View file

@ -217,7 +217,8 @@ struct FamilyReducer
else if (!reduction.uninhabited && !force)
{
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)
result.blockedTypes.insert(b);
@ -243,7 +244,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible)
{
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);
return false;
@ -251,7 +252,7 @@ struct FamilyReducer
else if (skip == SkipTestResult::Defer)
{
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>)
queuedTys.push_back(subject);
@ -269,7 +270,7 @@ struct FamilyReducer
if (skip == SkipTestResult::Irreducible)
{
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);
return false;
@ -277,7 +278,7 @@ struct FamilyReducer
else if (skip == SkipTestResult::Defer)
{
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>)
queuedTys.push_back(subject);
@ -346,7 +347,8 @@ struct FamilyReducer
return;
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);
}
}
@ -371,7 +373,8 @@ struct FamilyReducer
return;
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);
}
}
@ -385,8 +388,8 @@ struct FamilyReducer
}
};
static FamilyGraphReductionResult reduceFamiliesInternal(
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess,
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};
int iterationCount = 0;
@ -422,7 +425,8 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location,
if (collector.tys.empty() && collector.tps.empty())
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)
@ -441,7 +445,8 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
if (collector.tys.empty() && collector.tps.empty())
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)
@ -461,8 +466,8 @@ bool isPending(TypeId ty, ConstraintSolver* solver)
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
}
TypeFamilyReductionResult<TypeId> notFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -479,8 +484,8 @@ TypeFamilyReductionResult<TypeId> notFamilyFn(
return {ctx->builtins->booleanType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> lenFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -496,17 +501,18 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
return {std::nullopt, false, {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 (!normTy)
if (!normTy || inhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}};
// if the operand type is error suppressing, we can immediately reduce to `number`.
if (normTy->shouldSuppressErrors())
return {ctx->builtins->numberType, false, {}, {}};
// if we have a `never`, we can never observe that the operator didn't work.
if (is<NeverType>(operandTy))
// if we have an uninhabited type (like `never`), we can never observe that the operator didn't work.
if (inhabited == NormalizationResult::False)
return {ctx->builtins->neverType, false, {}, {}};
// if we're checking the length of a string, that works!
@ -555,8 +561,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
return {ctx->builtins->numberType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> unmFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
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.
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);
return {resultTy, false, {}, {}};
}
else
{
// TODO: We need to generalize `union<...>` type family to be variadic.
TypeId resultTy = ctx->arena->addType(UnionType{std::move(results)});
return {resultTy, false, {}, {}};
}
if (results.size() == 1)
return {results[0], false, {}, {}};
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
std::move(results),
{},
});
queue->add(resultTy);
return {resultTy, false, {}, {}};
}
// 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, {}, {}};
}
TypeFamilyReductionResult<TypeId> addFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -806,8 +805,8 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add");
}
TypeFamilyReductionResult<TypeId> subFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -818,8 +817,8 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub");
}
TypeFamilyReductionResult<TypeId> mulFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -830,8 +829,8 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul");
}
TypeFamilyReductionResult<TypeId> divFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -842,8 +841,8 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div");
}
TypeFamilyReductionResult<TypeId> idivFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -854,8 +853,8 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv");
}
TypeFamilyReductionResult<TypeId> powFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -866,8 +865,8 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow");
}
TypeFamilyReductionResult<TypeId> modFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -878,8 +877,8 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod");
}
TypeFamilyReductionResult<TypeId> concatFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -964,8 +963,8 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(
return {ctx->builtins->stringType, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> andFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1001,8 +1000,8 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(
return {overallResult.result, false, std::move(blockedTypes), {}};
}
TypeFamilyReductionResult<TypeId> orFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
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> 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 (!normLhsTy || !normRhsTy)
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}};
// if one of the types is error suppressing, we can just go ahead and reduce.
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
return {ctx->builtins->booleanType, false, {}, {}};
// if we have a `never`, we can never observe that the comparison didn't work.
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
// if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work.
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
return {ctx->builtins->booleanType, false, {}, {}};
// 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, {}, {}};
}
TypeFamilyReductionResult<TypeId> ltFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1165,8 +1166,8 @@ TypeFamilyReductionResult<TypeId> ltFamilyFn(
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt");
}
TypeFamilyReductionResult<TypeId> leFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1177,8 +1178,8 @@ TypeFamilyReductionResult<TypeId> leFamilyFn(
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le");
}
TypeFamilyReductionResult<TypeId> eqFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
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> 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 (!normLhsTy || !normRhsTy)
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
return {std::nullopt, false, {}, {}};
// 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, {}, {}};
// 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, {}, {}};
// findMetatableEntry demands the ability to emit errors, so we must give it
@ -1282,8 +1285,8 @@ struct FindRefinementBlockers : TypeOnceVisitor
};
TypeFamilyReductionResult<TypeId> refineFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 2 || !packParams.empty())
{
@ -1340,73 +1343,145 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(
return {resultTy, false, {}, {}};
}
TypeFamilyReductionResult<TypeId> unionFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
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");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
// if we only have one parameter, there's nothing to do.
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
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, false, {lhsTy}, {}};
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
return {rhsTy, false, {}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, false, {rhsTy}, {}};
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
return {lhsTy, false, {}, {}};
// we need to follow all of the type parameters.
std::vector<TypeId> types;
types.reserve(typeParams.size());
for (auto ty : typeParams)
types.emplace_back(follow(ty));
// unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type.
// this also will early return if _everything_ is `never`, since we already have to check that.
std::optional<TypeId> lastType = std::nullopt;
for (auto ty : types)
{
// if we have a previous type and it's not `never` and the current type isn't `never`...
if (lastType && !get<NeverType>(lastType) && !get<NeverType>(ty))
{
// we know we are not taking the short-circuited path.
lastType = std::nullopt;
break;
}
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy);
if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
if (get<NeverType>(ty))
continue;
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(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
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");
LUAU_ASSERT(false);
}
TypeId lhsTy = follow(typeParams.at(0));
TypeId rhsTy = follow(typeParams.at(1));
// if we only have one parameter, there's nothing to do.
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
if (isPending(lhsTy, ctx->solver))
return {std::nullopt, false, {lhsTy}, {}};
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
return {ctx->builtins->neverType, false, {}, {}};
else if (isPending(rhsTy, ctx->solver))
return {std::nullopt, false, {rhsTy}, {}};
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
return {ctx->builtins->neverType, false, {}, {}};
// we need to follow all of the type parameters.
std::vector<TypeId> types;
types.reserve(typeParams.size());
for (auto ty : typeParams)
types.emplace_back(follow(ty));
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy);
if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
// check to see if the operand types are resolved enough, and wait to reduce if not
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
for (auto ty : types)
{
if (isPending(ty, ctx->solver))
return {std::nullopt, false, {ty}, {}};
else if (get<NeverType>(ty))
return {ctx->builtins->neverType, false, {}, {}};
}
// fold over the types with `simplifyIntersection`
TypeId resultTy = ctx->builtins->unknownType;
for (auto ty : types)
{
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
if (!result.blockedTypes.empty())
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
resultTy = result.result;
}
// if the intersection simplifies to `never`, this gives us bad autocomplete.
// we'll just produce the intersection plainly instead, but this might be revisitable
// if we ever give `never` some kind of "explanation" trail.
if (get<NeverType>(result.result))
if (get<NeverType>(resultTy))
{
TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}});
TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
return {intersection, false, {}, {}};
}
return {result.result, false, {}, {}};
return {resultTy, false, {}, {}};
}
// computes the keys of `ty` into `result`
@ -1581,8 +1656,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
}
TypeFamilyReductionResult<TypeId> keyofFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -1593,8 +1668,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyFn(
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
}
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
{
if (typeParams.size() != 1 || !packParams.empty())
{
@ -1623,6 +1698,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
, leFamily{"le", leFamilyFn}
, eqFamily{"eq", eqFamilyFn}
, refineFamily{"refine", refineFamilyFn}
, singletonFamily{"singleton", singletonFamilyFn}
, unionFamily{"union", unionFamilyFn}
, intersectFamily{"intersect", intersectFamilyFn}
, keyofFamily{"keyof", keyofFamilyFn}

View file

@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAGVARIABLE(LuauMetatableInstantiationCloneCheck, false)
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
@ -5632,7 +5633,8 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
TypeId instantiated = *maybeInstantiated;
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);
TableType* ttv = getMutableTableType(target);

View file

@ -6,8 +6,6 @@
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauFollowEmptyTypePacks, false);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
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))
return btv->boundTo;
else if (const TypePack* tp = get<TypePack>(mapped);
(FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauFollowEmptyTypePacks) && tp && tp->head.empty())
else if (const TypePack* tp = get<TypePack>(mapped); tp && tp->head.empty())
return tp->tail;
else
return std::nullopt;

View file

@ -52,6 +52,11 @@ bool Index::operator==(const Index& other) const
return index == other.index;
}
bool Reduction::operator==(const Reduction& other) const
{
return resultType == other.resultType;
}
Path Path::append(const Path& suffix) const
{
std::vector<Component> joined(components);
@ -124,6 +129,11 @@ size_t PathHash::operator()(const PackField& field) const
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
{
return visit(*this, component);
@ -472,6 +482,14 @@ struct TraversalState
return false;
}
bool traverse(TypePath::Reduction reduction)
{
if (checkInvariants())
return false;
updateCurrent(reduction.resultType);
return true;
}
bool traverse(TypePath::PackField field)
{
if (checkInvariants())
@ -584,9 +602,14 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
result << "tail";
break;
}
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
{
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)
reportError(location, NormalizationTooComplex{});
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
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)
reportError(location, NormalizationTooComplex{});
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
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());
const TypeId leftType = follow(
leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()
);
const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type());
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
return true;
@ -52,7 +50,7 @@ static bool areCompatible(TypeId left, TypeId right)
return false;
};
for (const auto& [name, leftProp]: leftTable->props)
for (const auto& [name, leftProp] : leftTable->props)
{
auto it = rightTable->props.find(name);
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);
if (it == leftTable->props.end())
@ -75,6 +73,18 @@ static bool areCompatible(TypeId left, TypeId right)
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)
: arena(arena)
, builtinTypes(builtinTypes)
@ -82,6 +92,19 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
, ice(ice)
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
, 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:
// - never <: *blocked*
// - *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});
return true;
}
@ -473,8 +499,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
if (subTp == superTp)
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});
return true;
}

View file

@ -7,6 +7,8 @@
#include "lobject.h"
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
namespace Luau
{
namespace CodeGen
@ -833,6 +835,27 @@ void analyzeBytecodeTypes(IrFunction& function)
bcType.result = regTags[ra];
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_SETGLOBAL:
case LOP_CALL:
@ -866,7 +889,6 @@ void analyzeBytecodeTypes(IrFunction& function)
case LOP_COVERAGE:
case LOP_GETIMPORT:
case LOP_CAPTURE:
case LOP_NAMECALL:
case LOP_PREPVARARGS:
case LOP_GETVARARGS:
case LOP_FORGPREP:

View file

@ -163,6 +163,9 @@ bool isUnwindSupported()
{
#if defined(_WIN32) && defined(_M_X64)
return true;
#elif defined(__ANDROID__)
// Current unwind information is not compatible with Android
return false;
#elif defined(__APPLE__) && defined(__aarch64__)
char ver[256];
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)
{
CODEGEN_ASSERT(!FFlag::LuauCodegenContext);
int execDataSize = calculateExecDataSize(proto);
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))
{
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.bcodeCount = getInstructionCount(p->code, p->sizecode);
functionStat.irCount = unsigned(ir.function.instructions.size());

View file

@ -13,6 +13,7 @@
#include "ltm.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
namespace Luau
{
@ -1230,6 +1231,14 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
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);
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);
uint32_t aux = pc[1];
IrOp fallback = build.block(IrBlockKind::Fallback);
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
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);
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);
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 fallback = build.block(IrBlockKind::Fallback);
IrOp firstFastPathSuccess = 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));
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,
// or critical bugs that are found after the code has been submitted.
static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"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
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"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
// makes sure we always have at least one entry
nullptr,
};

View file

@ -1,6 +1,7 @@
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
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
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
SOFTWARE.
SOFTWARE.

View file

@ -929,7 +929,7 @@ static size_t gcstep(lua_State* L, size_t 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);

View file

@ -196,9 +196,9 @@ struct lua_Page
lua_Page* prev;
lua_Page* next;
// list of all gco pages
lua_Page* gcolistprev;
lua_Page* gcolistnext;
// list of all pages
lua_Page* listprev;
lua_Page* listnext;
int pageSize; // page size in bytes, including page header
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");
}
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;
@ -236,8 +236,8 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
page->prev = NULL;
page->next = NULL;
page->gcolistprev = NULL;
page->gcolistnext = NULL;
page->listprev = NULL;
page->listnext = NULL;
page->pageSize = pageSize;
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->busyBlocks = 0;
if (gcopageset)
if (pageset)
{
page->gcolistnext = *gcopageset;
if (page->gcolistnext)
page->gcolistnext->gcolistprev = page;
*gcopageset = page;
page->listnext = *pageset;
if (page->listnext)
page->listnext->listprev = page;
*pageset = 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
// 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
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)
{
@ -272,7 +272,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
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!)
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 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!)
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;
if (gcopageset)
if (pageset)
{
// remove page from alllist
if (page->gcolistnext)
page->gcolistnext->gcolistprev = page->gcolistprev;
if (page->listnext)
page->listnext->listprev = page->listprev;
if (page->gcolistprev)
page->gcolistprev->gcolistnext = page->gcolistnext;
else if (*gcopageset == page)
*gcopageset = page->gcolistnext;
if (page->listprev)
page->listprev->listnext = page->listnext;
else if (*pageset == page)
*pageset = page->listnext;
}
// so long
(*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
if (page->next)
@ -326,7 +326,7 @@ static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopa
else if (freepageset[sizeClass] == page)
freepageset[sizeClass] = page->next;
freepage(L, gcopageset, page);
freepage(L, pageset, page);
}
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;
}
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))
@ -684,7 +684,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l
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);

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_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_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 "lapi.h"
#include "lnumutils.h"
#include "lstate.h"
#include "ltable.h"
#include "lstring.h"
@ -10,6 +11,8 @@
#include "ldebug.h"
#include "lvm.h"
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false)
static int foreachi(lua_State* L)
{
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);
}
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
{
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 \
{ \
auto&& _m = (map); \
auto&& _k = (key); \
const size_t count = _m.count(_k); \
#define LUAU_CHECK_HAS_KEY(map, key) \
do \
{ \
auto&& _m = (map); \
auto&& _k = (key); \
const size_t count = _m.count(_k); \
CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
if (!count) \
{ \
MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v]: _m) \
{ \
MESSAGE("\tkey: " << k); \
} \
} \
if (!count) \
{ \
MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v] : _m) \
{ \
MESSAGE("\tkey: " << k); \
} \
} \
} while (false)
#define LUAU_CHECK_HAS_NO_KEY(map, key) do \
{ \
auto&& _m = (map); \
auto&& _k = (key); \
const size_t count = _m.count(_k); \
#define LUAU_CHECK_HAS_NO_KEY(map, key) \
do \
{ \
auto&& _m = (map); \
auto&& _k = (key); \
const size_t count = _m.count(_k); \
CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
if (count) \
{ \
MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v]: _m) \
{ \
MESSAGE("\tkey: " << k); \
} \
} \
if (count) \
{ \
MESSAGE("Keys: (count " << _m.size() << ")"); \
for (const auto& [k, v] : _m) \
{ \
MESSAGE("\tkey: " << k); \
} \
} \
} while (false)
TEST_SUITE_BEGIN("AutocompleteTest");

View file

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

View file

@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
LUAU_FASTFLAG(LuauCodegenInferNumTag)
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
LUAU_DYNAMIC_FASTFLAG(LuauFastCrossTableMove)
static lua_CompileOptions defaultOptions()
{
@ -415,6 +416,8 @@ TEST_CASE("Sort")
TEST_CASE("Move")
{
ScopedFastFlag luauFastCrossTableMove{DFFlag::LuauFastCrossTableMove, true};
runConformance("move.lua");
}
@ -1837,7 +1840,7 @@ TEST_CASE("DebugApi")
lua_pushnumber(L, 10);
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
}
@ -2174,8 +2177,7 @@ TEST_CASE("HugeFunctionLoadFailure")
static size_t largeAllocationToFail = 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)
{
free(ptr);

View file

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

View file

@ -14,6 +14,7 @@
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
static std::string getCodegenAssembly(const char* source)
{
@ -402,7 +403,7 @@ local function vecrcp(a: vector)
return vector(1, 2, 3) + a
end
)"),
R"(
R"(
; function vecrcp($arg0) line 2
bb_0:
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();

View file

@ -11,7 +11,9 @@
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauFixNormalizeCaching);
LUAU_FASTFLAG(LuauFixNormalizeCaching)
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
LUAU_FASTFLAG(LuauFixCyclicUnionsOfIntersections);
using namespace Luau;
@ -797,6 +799,36 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union")
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")
{
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);
}
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();

View file

@ -225,9 +225,9 @@ struct SubtypeFixture : Fixture
});
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
{"X", Property::readonly(builtinTypes->numberType)},
{"Y", Property::readonly(builtinTypes->numberType)},
});
{"X", Property::readonly(builtinTypes->numberType)},
{"Y", Property::readonly(builtinTypes->numberType)},
});
// "hello" | "hello"
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);
}
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_BEGIN("Subtyping.Subpaths");

View file

@ -944,7 +944,8 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
std::string expected;
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
expected = R"(Type
'{ 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);
REQUIRE(packPtr);
const TableType::Props theProps = {
{"BaseField", Property::readonly(builtinTypes->unknownType)},
{"BaseMethod", Property::readonly(arena.addType(
FunctionType{
thePack,
arena.addTypePack({})
}
))}
};
const TableType::Props theProps = {{"BaseField", Property::readonly(builtinTypes->unknownType)},
{"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))}};
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);
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_EQ(expected, toString(result.errors[0]));

View file

@ -10,7 +10,6 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauSetMetatableOnUnionsOfTables);
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")
{
ScopedFastFlag sff{FFlag::LuauSetMetatableOnUnionsOfTables, true};
CheckResult result = check(R"(
type A = {tag: "A", x: number}
type B = {tag: "B", y: string}

View file

@ -648,32 +648,20 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties")
unfreeze(arena);
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
getMutable<ClassType>(instanceType)->props = {
{"Parent", Property::rw(instanceType)}
};
getMutable<ClassType>(instanceType)->props = {{"Parent", Property::rw(instanceType)}};
//
TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
TypeId scriptType = arena.addType(ClassType{
"Script", {
{"Parent", Property::rw(workspaceType, instanceType)}
},
instanceType, nullopt, {}, {}, "Test"
});
TypeId scriptType =
arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test"});
TypeId partType = arena.addType(ClassType{
"Part", {
{"BrickColor", Property::rw(builtinTypes->stringType)},
{"Parent", Property::rw(workspaceType, instanceType)}
},
instanceType, nullopt, {}, {}, "Test"});
TypeId partType = arena.addType(
ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}},
instanceType, nullopt, {}, {}, "Test"});
getMutable<ClassType>(workspaceType)->props = {
{"Script", Property::readonly(scriptType)},
{"Part", Property::readonly(partType)}
};
getMutable<ClassType>(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}};
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);
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"));
}

View file

@ -20,6 +20,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTINT(LuauTarjanChildLimit);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferFunctions");
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);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
if (DFFlag::LuauImproveNonFunctionCallError)
{
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
}
else
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")
@ -2535,4 +2547,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_definition_in_a_do_block_with_globa
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();

View file

@ -17,6 +17,8 @@ using namespace Luau;
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties)
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TypeInferLoops");
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);
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")

View file

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

View file

@ -22,6 +22,9 @@ LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauReadWritePropertySyntax);
LUAU_FASTFLAG(LuauMetatableInstantiationCloneCheck);
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
TEST_SUITE_BEGIN("TableTests");
@ -2383,7 +2386,11 @@ b()
)");
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")
@ -3016,7 +3023,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
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
{
@ -3994,9 +4004,10 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
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"
"\n\tat [read \"b\"], boolean is not exactly string"
"\n\tat [read \"c\"], number is not exactly boolean";
std::string expected =
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
"\n\tat [read \"b\"], boolean is not exactly string"
"\n\tat [read \"c\"], number is not exactly boolean";
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")
{
ScopedFastFlag sff[] = {
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
CheckResult result = check(R"(
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")
{
ScopedFastFlag sff[] = {
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, false}
};
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
CheckResult result = check(R"(
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")
{
ScopedFastFlag sff[] = {
{FFlag::LuauReadWritePropertySyntax, true},
{FFlag::DebugLuauDeferredConstraintResolution, true}
};
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, true}};
CheckResult result = check(R"(
function oc(player, speaker)
@ -4206,8 +4208,8 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("<a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () "
"where "
"t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")));
"where "
"t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")));
}
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")));
}
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();

View file

@ -986,7 +986,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_found_this")
*/
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
{
(void) check(R"(
(void)check(R"(
local _
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")
{
(void) check(R"(
(void)check(R"(
local _
if _ then
while nil do

View file

@ -200,7 +200,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assignment_swap")
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"(
local function f(x): number?

View file

@ -12,6 +12,25 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
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")
{
CheckResult result = check(R"(
@ -572,7 +591,8 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
UnionType u;
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));

View file

@ -64,6 +64,39 @@ do
a = table.move({[minI] = 100}, minI, minI, maxI)
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
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.do_not_always_instantiate_generic_intersection_types
GenericsTests.do_not_infer_generic_functions
GenericsTests.dont_leak_generic_types
GenericsTests.dont_leak_inferred_generic_types
GenericsTests.dont_substitute_bound_types
GenericsTests.error_detailed_function_mismatch_generic_pack
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_if_it_is_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.array_factory_function
TableTests.casting_tables_with_props_into_table_with_indexer2
@ -395,6 +392,8 @@ TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.string_index
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_sorta_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.table_properties_type_error_escapes
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
UnionTypes.error_detailed_optional
UnionTypes.error_detailed_union_all