mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/622 (#1232)
# What's changed? * Improved the actual message for the type errors for `cannot call non-function` when attempting to call a union of functions/callable tables. The error now correctly explains the issue is an inability to determine the return type of the call in this situation. * Resolve an issue where tables and metatables were not correctly being cloned during instantiation (fixes #1176). * Refactor `luaM_getnextgcopage` to `luaM_getnextpage` (generally removing `gco` prefix where appropriate). * Optimize `table.move` between tables for large ranges of elements. * Reformat a bunch of code automatically using `clang-format`. ### New Type Solver * Clean up minimally-used or unused constraints in the constraint solver (`InstantiationConstraint`, `SetOpConstraint`, `SingletonOrTopTypeConstraint`). * Add a builtin `singleton` type family to replace `SingletonOrTopTypeConstraint` when inferring refinements. * Fixed a crash involving type path reasoning by recording when type family reduction has taken place in the path. * Improved constraint ordering by blocking on unreduced types families that are not yet proven uninhabitable. * Improved the handling of `SetIndexerConstraints` for both better inference quality and to resolve crashes. * Fix a crash when normalizing cyclic unions of intersections. * Fix a crash when normalizing an intersection with the negation of `unknown`. * Fix a number of crashes caused by missing `follow` calls on `TypeId`s. * Changed type family reduction to correctly use a semantic notion of uninhabited types, rather than checking for `never` types specifically. * Refactor the `union` and `intersect` type families to be variadic. ### Native Code Generation * Improve translation for userdata key get/set and userdata/vector namecall. * Provide `[top level]` and `[anonymous]` as function names to `FunctionStats` as appropriate when no function name is available. * Disable unwind support on Android platforms since it is unsupported. * --- ### Internal Contributors Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
9c2146288d
commit
68bd1b2349
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -92,7 +92,7 @@ struct BlockedTypePack
|
||||
*/
|
||||
struct TypeFamilyInstanceTypePack
|
||||
{
|
||||
NotNull<TypePackFamily> family;
|
||||
NotNull<const TypePackFamily> family;
|
||||
|
||||
std::vector<TypeId> typeArguments;
|
||||
std::vector<TypePackId> packArguments;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.refineFamily},
|
||||
{ty, dt},
|
||||
{},
|
||||
}, scope, location);
|
||||
},
|
||||
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)));
|
||||
}
|
||||
|
||||
@ -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{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.notFamily},
|
||||
{operandType},
|
||||
{},
|
||||
}, scope, unary->location);
|
||||
},
|
||||
scope, unary->location);
|
||||
return Inference{resultType, refinementArena.negation(refinement)};
|
||||
}
|
||||
case AstExprUnary::Op::Len:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.lenFamily},
|
||||
{operandType},
|
||||
{},
|
||||
}, scope, unary->location);
|
||||
},
|
||||
scope, unary->location);
|
||||
return Inference{resultType, refinementArena.negation(refinement)};
|
||||
}
|
||||
case AstExprUnary::Op::Minus:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.unmFamily},
|
||||
{operandType},
|
||||
{},
|
||||
}, scope, unary->location);
|
||||
},
|
||||
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{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.addFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Sub:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.subFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Mul:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.mulFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Div:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.divFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::FloorDiv:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.idivFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Pow:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.powFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Mod:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.modFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Concat:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.concatFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::And:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.andFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Or:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.orFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareEq:
|
||||
case AstExprBinary::Op::CompareNe:
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.eqFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
}, scope, binary->location);
|
||||
},
|
||||
scope, binary->location);
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Op__Count:
|
||||
@ -2613,13 +2662,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||
|
||||
TypeId indexKey = indexKeyLowerBound.size() == 1
|
||||
? *indexKeyLowerBound.begin()
|
||||
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())})
|
||||
;
|
||||
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
|
||||
|
||||
TypeId indexValue = indexValueLowerBound.size() == 1
|
||||
? *indexValueLowerBound.begin()
|
||||
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())})
|
||||
;
|
||||
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
|
||||
|
||||
ttv->indexer = TableIndexer{indexKey, indexValue};
|
||||
}
|
||||
@ -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{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||
{lhs, rhs},
|
||||
{},
|
||||
}, scope, location);
|
||||
},
|
||||
scope, location);
|
||||
|
||||
return resultType;
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
||||
{
|
||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
||||
TypeId resultType = createFamilyInstance(
|
||||
TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.intersectFamily},
|
||||
{lhs, rhs},
|
||||
{},
|
||||
}, scope, location);
|
||||
},
|
||||
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};
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
++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)
|
||||
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
@ -365,17 +365,9 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
|
||||
|
||||
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
|
||||
// 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);
|
||||
@ -389,17 +381,9 @@ std::optional<TypeId> selectOverload(
|
||||
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)
|
||||
|
@ -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)
|
||||
@ -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});
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
@ -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);
|
||||
@ -427,4 +413,4 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
||||
return exprType;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Luau
|
||||
|
@ -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>)
|
||||
|
@ -546,11 +546,13 @@ 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)
|
||||
|
@ -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)
|
||||
|
@ -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};
|
||||
}
|
||||
|
@ -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);
|
||||
@ -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,10 +738,10 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
||||
// though there exists no arm of the union that is inhabited or have a reduced type.
|
||||
ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?");
|
||||
}
|
||||
else if (results.size() == 1)
|
||||
|
||||
if (results.size() == 1)
|
||||
return {results[0], false, {}, {}};
|
||||
else if (results.size() == 2)
|
||||
{
|
||||
|
||||
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||
std::move(results),
|
||||
@ -745,13 +751,6 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
||||
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, {}, {}};
|
||||
}
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -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 (get<NeverType>(ty))
|
||||
continue;
|
||||
lastType = ty;
|
||||
}
|
||||
|
||||
// if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
|
||||
if (lastType)
|
||||
return {lastType, false, {}, {}};
|
||||
|
||||
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||
for (auto ty : types)
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, false, {ty}, {}};
|
||||
|
||||
// fold over the types with `simplifyUnion`
|
||||
TypeId resultTy = ctx->builtins->neverType;
|
||||
for (auto ty : types)
|
||||
{
|
||||
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
||||
if (!result.blockedTypes.empty())
|
||||
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||
|
||||
return {result.result, false, {}, {}};
|
||||
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);
|
||||
// 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}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-2023 Roblox Corporation
|
||||
Copyright (c) 2019-2024 Roblox Corporation
|
||||
Copyright (c) 1994–2019 Lua.org, PUC-Rio.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -145,7 +145,8 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
|
||||
{
|
||||
};
|
||||
|
||||
#define LUAU_CHECK_HAS_KEY(map, key) do \
|
||||
#define LUAU_CHECK_HAS_KEY(map, key) \
|
||||
do \
|
||||
{ \
|
||||
auto&& _m = (map); \
|
||||
auto&& _k = (key); \
|
||||
@ -161,7 +162,8 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define LUAU_CHECK_HAS_NO_KEY(map, key) do \
|
||||
#define LUAU_CHECK_HAS_NO_KEY(map, key) \
|
||||
do \
|
||||
{ \
|
||||
auto&& _m = (map); \
|
||||
auto&& _k = (key); \
|
||||
|
@ -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{{
|
||||
{"__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");
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
@ -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};
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
|
||||
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
|
||||
|
||||
static std::string getCodegenAssembly(const char* source)
|
||||
{
|
||||
@ -420,4 +421,128 @@ bb_bytecode_1:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("VectorNamecall")
|
||||
{
|
||||
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function abs(a: vector)
|
||||
return a:Abs()
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function abs($arg0) line 2
|
||||
bb_0:
|
||||
CHECK_TAG R0, tvector, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
FALLBACK_NAMECALL 0u, R1, R0, K0
|
||||
INTERRUPT 2u
|
||||
SET_SAVEDPC 3u
|
||||
CALL R1, 1i, -1i
|
||||
INTERRUPT 3u
|
||||
RETURN R1, -1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("UserDataGetIndex")
|
||||
{
|
||||
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function getxy(a: Point)
|
||||
return a.x + a.y
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function getxy($arg0) line 2
|
||||
bb_0:
|
||||
CHECK_TAG R0, tuserdata, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
FALLBACK_GETTABLEKS 0u, R2, R0, K0
|
||||
FALLBACK_GETTABLEKS 2u, R3, R0, K1
|
||||
CHECK_TAG R2, tnumber, bb_fallback_3
|
||||
CHECK_TAG R3, tnumber, bb_fallback_3
|
||||
%14 = LOAD_DOUBLE R2
|
||||
%16 = ADD_NUM %14, R3
|
||||
STORE_DOUBLE R1, %16
|
||||
STORE_TAG R1, tnumber
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
INTERRUPT 5u
|
||||
RETURN R1, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("UserDataSetIndex")
|
||||
{
|
||||
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function setxy(a: Point)
|
||||
a.x = 3
|
||||
a.y = 4
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function setxy($arg0) line 2
|
||||
bb_0:
|
||||
CHECK_TAG R0, tuserdata, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
STORE_DOUBLE R1, 3
|
||||
STORE_TAG R1, tnumber
|
||||
FALLBACK_SETTABLEKS 1u, R1, R0, K0
|
||||
STORE_DOUBLE R1, 4
|
||||
FALLBACK_SETTABLEKS 4u, R1, R0, K1
|
||||
INTERRUPT 6u
|
||||
RETURN R0, 0i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("UserDataNamecall")
|
||||
{
|
||||
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function getxy(a: Point)
|
||||
return a:GetX() + a:GetY()
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function getxy($arg0) line 2
|
||||
bb_0:
|
||||
CHECK_TAG R0, tuserdata, exit(entry)
|
||||
JUMP bb_2
|
||||
bb_2:
|
||||
JUMP bb_bytecode_1
|
||||
bb_bytecode_1:
|
||||
FALLBACK_NAMECALL 0u, R2, R0, K0
|
||||
INTERRUPT 2u
|
||||
SET_SAVEDPC 3u
|
||||
CALL R2, 1i, 1i
|
||||
FALLBACK_NAMECALL 3u, R3, R0, K1
|
||||
INTERRUPT 5u
|
||||
SET_SAVEDPC 6u
|
||||
CALL R3, 1i, 1i
|
||||
CHECK_TAG R2, tnumber, bb_fallback_3
|
||||
CHECK_TAG R3, tnumber, bb_fallback_3
|
||||
%20 = LOAD_DOUBLE R2
|
||||
%22 = ADD_NUM %20, R3
|
||||
STORE_DOUBLE R1, %22
|
||||
STORE_TAG R1, tnumber
|
||||
JUMP bb_4
|
||||
bb_4:
|
||||
INTERRUPT 7u
|
||||
RETURN R1, 1i
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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});
|
||||
|
||||
|
@ -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]));
|
||||
|
@ -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}
|
||||
|
@ -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)}
|
||||
},
|
||||
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"));
|
||||
}
|
||||
|
@ -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,11 +2131,21 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
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();
|
||||
|
@ -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,6 +167,10 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
|
||||
else
|
||||
CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
|
||||
}
|
||||
|
||||
|
@ -592,6 +592,9 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil :)
|
||||
else
|
||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
|
||||
}
|
||||
|
||||
|
@ -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,6 +2386,10 @@ b()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
else
|
||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||
}
|
||||
|
||||
@ -3016,6 +3023,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||
CHECK("Cannot call a value of type { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
||||
else
|
||||
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
||||
}
|
||||
else
|
||||
@ -3994,7 +4004,8 @@ 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"
|
||||
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)
|
||||
@ -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();
|
||||
|
@ -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?
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user