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;
|
std::vector<TypeId> interiorTypes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// subType ~ inst superType
|
|
||||||
struct InstantiationConstraint
|
|
||||||
{
|
|
||||||
TypeId subType;
|
|
||||||
TypeId superType;
|
|
||||||
};
|
|
||||||
|
|
||||||
// variables ~ iterate iterator
|
// variables ~ iterate iterator
|
||||||
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
|
// Unpack the iterator, figure out what types it iterates over, and bind those types to variables.
|
||||||
struct IterableConstraint
|
struct IterableConstraint
|
||||||
@ -229,17 +222,6 @@ struct SetIndexerConstraint
|
|||||||
TypeId propType;
|
TypeId propType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// if negation:
|
|
||||||
// result ~ if isSingleton D then ~D else unknown where D = discriminantType
|
|
||||||
// if not negation:
|
|
||||||
// result ~ if isSingleton D then D else unknown where D = discriminantType
|
|
||||||
struct SingletonOrTopTypeConstraint
|
|
||||||
{
|
|
||||||
TypeId resultType;
|
|
||||||
TypeId discriminantType;
|
|
||||||
bool negated;
|
|
||||||
};
|
|
||||||
|
|
||||||
// resultType ~ unpack sourceTypePack
|
// resultType ~ unpack sourceTypePack
|
||||||
//
|
//
|
||||||
// Similar to PackSubtypeConstraint, but with one important difference: If the
|
// Similar to PackSubtypeConstraint, but with one important difference: If the
|
||||||
@ -269,22 +251,6 @@ struct Unpack1Constraint
|
|||||||
bool resultIsLValue = false;
|
bool resultIsLValue = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// resultType ~ T0 op T1 op ... op TN
|
|
||||||
//
|
|
||||||
// op is either union or intersection. If any of the input types are blocked,
|
|
||||||
// this constraint will block unless forced.
|
|
||||||
struct SetOpConstraint
|
|
||||||
{
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Intersection,
|
|
||||||
Union
|
|
||||||
} mode;
|
|
||||||
|
|
||||||
TypeId resultType;
|
|
||||||
std::vector<TypeId> types;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ty ~ reduce ty
|
// ty ~ reduce ty
|
||||||
//
|
//
|
||||||
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
|
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
|
||||||
@ -301,10 +267,9 @@ struct ReducePackConstraint
|
|||||||
TypePackId tp;
|
TypePackId tp;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, IterableConstraint, NameConstraint,
|
||||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
||||||
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint,
|
HasIndexerConstraint, SetIndexerConstraint, UnpackConstraint, Unpack1Constraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
|
||||||
SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
|
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
|
@ -91,6 +91,9 @@ struct ConstraintSolver
|
|||||||
// A mapping from free types to the number of unresolved constraints that mention them.
|
// A mapping from free types to the number of unresolved constraints that mention them.
|
||||||
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
|
DenseHashMap<TypeId, size_t> unresolvedConstraints{{}};
|
||||||
|
|
||||||
|
// Irreducible/uninhabited type families or type pack families.
|
||||||
|
DenseHashSet<const void*> uninhabitedTypeFamilies{{}};
|
||||||
|
|
||||||
// Recorded errors that take place within the solver.
|
// Recorded errors that take place within the solver.
|
||||||
ErrorVec errors;
|
ErrorVec errors;
|
||||||
|
|
||||||
@ -124,7 +127,6 @@ struct ConstraintSolver
|
|||||||
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force);
|
|
||||||
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
|
||||||
@ -134,22 +136,18 @@ struct ConstraintSolver
|
|||||||
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
bool tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen);
|
bool tryDispatchHasIndexer(
|
||||||
|
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen);
|
||||||
bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
/// (dispatched, found) where
|
std::pair<bool, std::optional<TypeId>> tryDispatchSetIndexer(
|
||||||
/// - dispatched: this constraint can be considered having dispatched.
|
NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
|
||||||
/// - found: true if adding an indexer for a particular type was allowed.
|
|
||||||
std::pair<bool, bool> tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
|
|
||||||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
|
||||||
|
|
||||||
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
|
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
|
||||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);
|
||||||
|
|
||||||
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
|
||||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
@ -69,7 +69,8 @@ private:
|
|||||||
|
|
||||||
struct SolveResult
|
struct SolveResult
|
||||||
{
|
{
|
||||||
enum OverloadCallResult {
|
enum OverloadCallResult
|
||||||
|
{
|
||||||
Ok,
|
Ok,
|
||||||
CodeTooComplex,
|
CodeTooComplex,
|
||||||
OccursCheckFailed,
|
OccursCheckFailed,
|
||||||
@ -87,16 +88,8 @@ struct SolveResult
|
|||||||
// Helper utility, presently used for binary operator type families.
|
// Helper utility, presently used for binary operator type families.
|
||||||
//
|
//
|
||||||
// Given a function and a set of arguments, select a suitable overload.
|
// Given a function and a set of arguments, select a suitable overload.
|
||||||
SolveResult solveFunctionCall(
|
SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, const Location& location, TypeId fn,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
TypePackId argsPack);
|
||||||
NotNull<Normalizer> normalizer,
|
|
||||||
NotNull<InternalErrorReporter> iceReporter,
|
|
||||||
NotNull<TypeCheckLimits> limits,
|
|
||||||
NotNull<Scope> scope,
|
|
||||||
const Location& location,
|
|
||||||
TypeId fn,
|
|
||||||
TypePackId argsPack
|
|
||||||
);
|
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
@ -208,7 +208,8 @@ private:
|
|||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
|
||||||
|
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm);
|
SubtypingResult isCovariantWith(
|
||||||
|
SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);
|
||||||
|
@ -17,4 +17,4 @@ class AstExpr;
|
|||||||
TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
|
TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
|
||||||
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Unifier2> unifier, TypeId expectedType, TypeId exprType,
|
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Unifier2> unifier, TypeId expectedType, TypeId exprType,
|
||||||
const AstExpr* expr, std::vector<TypeId>& toBlock);
|
const AstExpr* expr, std::vector<TypeId>& toBlock);
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -552,6 +552,27 @@ struct TypeFamilyInstanceType
|
|||||||
|
|
||||||
std::vector<TypeId> typeArguments;
|
std::vector<TypeId> typeArguments;
|
||||||
std::vector<TypePackId> packArguments;
|
std::vector<TypePackId> packArguments;
|
||||||
|
|
||||||
|
TypeFamilyInstanceType(NotNull<const TypeFamily> family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||||
|
: family(family)
|
||||||
|
, typeArguments(typeArguments)
|
||||||
|
, packArguments(packArguments)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyInstanceType(const TypeFamily& family, std::vector<TypeId> typeArguments)
|
||||||
|
: family{&family}
|
||||||
|
, typeArguments(typeArguments)
|
||||||
|
, packArguments{}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyInstanceType(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||||
|
: family{&family}
|
||||||
|
, typeArguments(typeArguments)
|
||||||
|
, packArguments(packArguments)
|
||||||
|
{
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Represents a pending type alias instantiation.
|
/** Represents a pending type alias instantiation.
|
||||||
|
@ -48,6 +48,11 @@ struct TypeArena
|
|||||||
{
|
{
|
||||||
return addTypePack(TypePackVar(std::move(tp)));
|
return addTypePack(TypePackVar(std::move(tp)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId addTypeFamily(const TypeFamily& family, std::initializer_list<TypeId> types);
|
||||||
|
TypeId addTypeFamily(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
|
||||||
|
TypePackId addTypePackFamily(const TypePackFamily& family, std::initializer_list<TypeId> types);
|
||||||
|
TypePackId addTypePackFamily(const TypePackFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments = {});
|
||||||
};
|
};
|
||||||
|
|
||||||
void freeze(TypeArena& arena);
|
void freeze(TypeArena& arena);
|
||||||
|
@ -99,8 +99,8 @@ struct TypeFamilyReductionResult
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
using ReducerFunction =
|
using ReducerFunction = std::function<TypeFamilyReductionResult<T>(
|
||||||
std::function<TypeFamilyReductionResult<T>(T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
|
T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
|
||||||
|
|
||||||
/// Represents a type function that may be applied to map a series of types and
|
/// Represents a type function that may be applied to map a series of types and
|
||||||
/// type packs to a single output type.
|
/// type packs to a single output type.
|
||||||
@ -189,6 +189,7 @@ struct BuiltinTypeFamilies
|
|||||||
TypeFamily eqFamily;
|
TypeFamily eqFamily;
|
||||||
|
|
||||||
TypeFamily refineFamily;
|
TypeFamily refineFamily;
|
||||||
|
TypeFamily singletonFamily;
|
||||||
TypeFamily unionFamily;
|
TypeFamily unionFamily;
|
||||||
TypeFamily intersectFamily;
|
TypeFamily intersectFamily;
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ struct BlockedTypePack
|
|||||||
*/
|
*/
|
||||||
struct TypeFamilyInstanceTypePack
|
struct TypeFamilyInstanceTypePack
|
||||||
{
|
{
|
||||||
NotNull<TypePackFamily> family;
|
NotNull<const TypePackFamily> family;
|
||||||
|
|
||||||
std::vector<TypeId> typeArguments;
|
std::vector<TypeId> typeArguments;
|
||||||
std::vector<TypePackId> packArguments;
|
std::vector<TypePackId> packArguments;
|
||||||
|
@ -79,9 +79,18 @@ enum class PackField
|
|||||||
Tail,
|
Tail,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Component that represents the result of a reduction
|
||||||
|
/// `resultType` is `never` if the reduction could not proceed
|
||||||
|
struct Reduction
|
||||||
|
{
|
||||||
|
TypeId resultType;
|
||||||
|
|
||||||
|
bool operator==(const Reduction& other) const;
|
||||||
|
};
|
||||||
|
|
||||||
/// A single component of a path, representing one inner type or type pack to
|
/// A single component of a path, representing one inner type or type pack to
|
||||||
/// traverse into.
|
/// traverse into.
|
||||||
using Component = Luau::Variant<Property, Index, TypeField, PackField>;
|
using Component = Luau::Variant<Property, Index, TypeField, PackField, Reduction>;
|
||||||
|
|
||||||
/// A path through a type or type pack accessing a particular type or type pack
|
/// A path through a type or type pack accessing a particular type or type pack
|
||||||
/// contained within.
|
/// contained within.
|
||||||
@ -156,6 +165,7 @@ struct PathHash
|
|||||||
size_t operator()(const Index& idx) const;
|
size_t operator()(const Index& idx) const;
|
||||||
size_t operator()(const TypeField& field) const;
|
size_t operator()(const TypeField& field) const;
|
||||||
size_t operator()(const PackField& field) const;
|
size_t operator()(const PackField& field) const;
|
||||||
|
size_t operator()(const Reduction& reduction) const;
|
||||||
size_t operator()(const Component& component) const;
|
size_t operator()(const Component& component) const;
|
||||||
size_t operator()(const Path& path) const;
|
size_t operator()(const Path& path) const;
|
||||||
};
|
};
|
||||||
|
@ -48,8 +48,12 @@ struct Unifier2
|
|||||||
int recursionLimit = 0;
|
int recursionLimit = 0;
|
||||||
|
|
||||||
std::vector<ConstraintV> incompleteSubtypes;
|
std::vector<ConstraintV> incompleteSubtypes;
|
||||||
|
// null if not in a constraint solving context
|
||||||
|
DenseHashSet<const void*>* uninhabitedTypeFamilies;
|
||||||
|
|
||||||
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice);
|
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice);
|
||||||
|
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice,
|
||||||
|
DenseHashSet<const void*>* uninhabitedTypeFamilies);
|
||||||
|
|
||||||
/** Attempt to commit the subtype relation subTy <: superTy to the type
|
/** Attempt to commit the subtype relation subTy <: superTy to the type
|
||||||
* graph.
|
* graph.
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauSetMetatableOnUnionsOfTables, false);
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
|
LUAU_FASTFLAGVARIABLE(LuauMakeStringMethodsChecked, false);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -1067,7 +1066,7 @@ static std::optional<WithPredicate<TypePackId>> magicFunctionSetMetaTable(
|
|||||||
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
|
else if (get<AnyType>(target) || get<ErrorType>(target) || isTableIntersection(target))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
else if (FFlag::LuauSetMetatableOnUnionsOfTables && isTableUnion(target))
|
else if (isTableUnion(target))
|
||||||
{
|
{
|
||||||
const UnionType* ut = get<UnionType>(target);
|
const UnionType* ut = get<UnionType>(target);
|
||||||
LUAU_ASSERT(ut);
|
LUAU_ASSERT(ut);
|
||||||
|
@ -233,7 +233,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||||||
Checkpoint end = checkpoint(this);
|
Checkpoint end = checkpoint(this);
|
||||||
|
|
||||||
TypeId result = arena->addType(BlockedType{});
|
TypeId result = arena->addType(BlockedType{});
|
||||||
NotNull<Constraint> genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())});
|
NotNull<Constraint> genConstraint =
|
||||||
|
addConstraint(scope, block->location, GeneralizationConstraint{result, moduleFnTy, std::move(interiorTypes.back())});
|
||||||
getMutable<BlockedType>(result)->setOwner(genConstraint);
|
getMutable<BlockedType>(result)->setOwner(genConstraint);
|
||||||
forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
|
forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
|
||||||
genConstraint->dependencies.push_back(NotNull{c.get()});
|
genConstraint->dependencies.push_back(NotNull{c.get()});
|
||||||
@ -407,13 +408,13 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca
|
|||||||
else if (auto proposition = get<Proposition>(refinement))
|
else if (auto proposition = get<Proposition>(refinement))
|
||||||
{
|
{
|
||||||
TypeId discriminantTy = proposition->discriminantTy;
|
TypeId discriminantTy = proposition->discriminantTy;
|
||||||
if (!sense && !eq)
|
|
||||||
discriminantTy = arena->addType(NegationType{proposition->discriminantTy});
|
// if we have a negative sense, then we need to negate the discriminant
|
||||||
else if (eq)
|
if (!sense)
|
||||||
{
|
discriminantTy = arena->addType(NegationType{discriminantTy});
|
||||||
discriminantTy = arena->addType(BlockedType{});
|
|
||||||
constraints->push_back(SingletonOrTopTypeConstraint{discriminantTy, proposition->discriminantTy, !sense});
|
if (eq)
|
||||||
}
|
discriminantTy = arena->addTypeFamily(kBuiltinTypeFamilies.singletonFamily, {discriminantTy});
|
||||||
|
|
||||||
for (const RefinementKey* key = proposition->key; key; key = key->parent)
|
for (const RefinementKey* key = proposition->key; key; key = key->parent)
|
||||||
{
|
{
|
||||||
@ -525,11 +526,13 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
|
|||||||
{
|
{
|
||||||
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.refineFamily},
|
TypeFamilyInstanceType{
|
||||||
{ty, dt},
|
NotNull{&kBuiltinTypeFamilies.refineFamily},
|
||||||
{},
|
{ty, dt},
|
||||||
}, scope, location);
|
{},
|
||||||
|
},
|
||||||
|
scope, location);
|
||||||
|
|
||||||
ty = resultType;
|
ty = resultType;
|
||||||
}
|
}
|
||||||
@ -961,7 +964,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||||||
// With or without self
|
// With or without self
|
||||||
|
|
||||||
TypeId generalizedType = arena->addType(BlockedType{});
|
TypeId generalizedType = arena->addType(BlockedType{});
|
||||||
|
|
||||||
Checkpoint start = checkpoint(this);
|
Checkpoint start = checkpoint(this);
|
||||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||||
@ -1056,7 +1058,16 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (auto blocked = getMutable<BlockedType>(generalizedType))
|
|
||||||
|
// We need to check if the blocked type has no owner here because
|
||||||
|
// if a function is defined twice anywhere in the program like:
|
||||||
|
// `function f() end` and then later like `function f() end`
|
||||||
|
// Then there will be exactly one definition in the scope for it because it's a global
|
||||||
|
// (this is the same as writing f = function() end)
|
||||||
|
// Therefore, when we visit() the multiple different expression of this global variable
|
||||||
|
// They will all be aliased to the same blocked type, which means we can create multiple constraints
|
||||||
|
// for the same blocked type.
|
||||||
|
if (auto blocked = getMutable<BlockedType>(generalizedType); blocked && !blocked->getOwner())
|
||||||
blocked->setOwner(addConstraint(scope, std::move(c)));
|
blocked->setOwner(addConstraint(scope, std::move(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1162,7 +1173,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
|
|||||||
|
|
||||||
if (typeState)
|
if (typeState)
|
||||||
{
|
{
|
||||||
NotNull<Constraint> uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/ true});
|
NotNull<Constraint> uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/true});
|
||||||
if (auto blocked = getMutable<BlockedType>(*typeState); blocked && !blocked->getOwner())
|
if (auto blocked = getMutable<BlockedType>(*typeState); blocked && !blocked->getOwner())
|
||||||
blocked->setOwner(uc);
|
blocked->setOwner(uc);
|
||||||
|
|
||||||
@ -1178,7 +1189,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
|
|||||||
|
|
||||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifStatement)
|
||||||
{
|
{
|
||||||
RefinementId refinement = [&](){
|
RefinementId refinement = [&]() {
|
||||||
InConditionalContext flipper{&typeContext};
|
InConditionalContext flipper{&typeContext};
|
||||||
return check(scope, ifStatement->condition, std::nullopt).refinement;
|
return check(scope, ifStatement->condition, std::nullopt).refinement;
|
||||||
}();
|
}();
|
||||||
@ -1708,8 +1719,8 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||||||
* 4. Solve the call
|
* 4. Solve the call
|
||||||
*/
|
*/
|
||||||
|
|
||||||
NotNull<Constraint> checkConstraint =
|
NotNull<Constraint> checkConstraint = addConstraint(scope, call->func->location,
|
||||||
addConstraint(scope, call->func->location, FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}});
|
FunctionCheckConstraint{fnType, argPack, call, NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}});
|
||||||
|
|
||||||
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) {
|
forEachConstraint(funcBeginCheckpoint, funcEndCheckpoint, this, [checkConstraint](const ConstraintPtr& constraint) {
|
||||||
checkConstraint->dependencies.emplace_back(constraint.get());
|
checkConstraint->dependencies.emplace_back(constraint.get());
|
||||||
@ -1901,7 +1912,8 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
|
|||||||
scope->rvalueRefinements[key->def] = result;
|
scope->rvalueRefinements[key->def] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)});
|
auto c =
|
||||||
|
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)});
|
||||||
getMutable<BlockedType>(result)->setOwner(c);
|
getMutable<BlockedType>(result)->setOwner(c);
|
||||||
|
|
||||||
if (key)
|
if (key)
|
||||||
@ -1957,7 +1969,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
|||||||
Checkpoint endCheckpoint = checkpoint(this);
|
Checkpoint endCheckpoint = checkpoint(this);
|
||||||
|
|
||||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
|
NotNull<Constraint> gc =
|
||||||
|
addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
|
||||||
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
|
getMutable<BlockedType>(generalizedTy)->setOwner(gc);
|
||||||
interiorTypes.pop_back();
|
interiorTypes.pop_back();
|
||||||
|
|
||||||
@ -1992,29 +2005,35 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
|
|||||||
{
|
{
|
||||||
case AstExprUnary::Op::Not:
|
case AstExprUnary::Op::Not:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.notFamily},
|
TypeFamilyInstanceType{
|
||||||
{operandType},
|
NotNull{&kBuiltinTypeFamilies.notFamily},
|
||||||
{},
|
{operandType},
|
||||||
}, scope, unary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, unary->location);
|
||||||
return Inference{resultType, refinementArena.negation(refinement)};
|
return Inference{resultType, refinementArena.negation(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprUnary::Op::Len:
|
case AstExprUnary::Op::Len:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.lenFamily},
|
TypeFamilyInstanceType{
|
||||||
{operandType},
|
NotNull{&kBuiltinTypeFamilies.lenFamily},
|
||||||
{},
|
{operandType},
|
||||||
}, scope, unary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, unary->location);
|
||||||
return Inference{resultType, refinementArena.negation(refinement)};
|
return Inference{resultType, refinementArena.negation(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprUnary::Op::Minus:
|
case AstExprUnary::Op::Minus:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.unmFamily},
|
TypeFamilyInstanceType{
|
||||||
{operandType},
|
NotNull{&kBuiltinTypeFamilies.unmFamily},
|
||||||
{},
|
{operandType},
|
||||||
}, scope, unary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, unary->location);
|
||||||
return Inference{resultType, refinementArena.negation(refinement)};
|
return Inference{resultType, refinementArena.negation(refinement)};
|
||||||
}
|
}
|
||||||
default: // msvc can't prove that this is exhaustive.
|
default: // msvc can't prove that this is exhaustive.
|
||||||
@ -2030,138 +2049,168 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar
|
|||||||
{
|
{
|
||||||
case AstExprBinary::Op::Add:
|
case AstExprBinary::Op::Add:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.addFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.addFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Sub:
|
case AstExprBinary::Op::Sub:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.subFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.subFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Mul:
|
case AstExprBinary::Op::Mul:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.mulFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.mulFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Div:
|
case AstExprBinary::Op::Div:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.divFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.divFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::FloorDiv:
|
case AstExprBinary::Op::FloorDiv:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.idivFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.idivFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Pow:
|
case AstExprBinary::Op::Pow:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.powFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.powFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Mod:
|
case AstExprBinary::Op::Mod:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.modFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.modFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Concat:
|
case AstExprBinary::Op::Concat:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.concatFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.concatFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::And:
|
case AstExprBinary::Op::And:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.andFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.andFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Or:
|
case AstExprBinary::Op::Or:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.orFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.orFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::CompareLt:
|
case AstExprBinary::Op::CompareLt:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::CompareGe:
|
case AstExprBinary::Op::CompareGe:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
TypeFamilyInstanceType{
|
||||||
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
|
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||||
{},
|
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::CompareLe:
|
case AstExprBinary::Op::CompareLe:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::CompareGt:
|
case AstExprBinary::Op::CompareGt:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
TypeFamilyInstanceType{
|
||||||
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
|
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||||
{},
|
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::CompareEq:
|
case AstExprBinary::Op::CompareEq:
|
||||||
case AstExprBinary::Op::CompareNe:
|
case AstExprBinary::Op::CompareNe:
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.eqFamily},
|
TypeFamilyInstanceType{
|
||||||
{leftType, rightType},
|
NotNull{&kBuiltinTypeFamilies.eqFamily},
|
||||||
{},
|
{leftType, rightType},
|
||||||
}, scope, binary->location);
|
{},
|
||||||
|
},
|
||||||
|
scope, binary->location);
|
||||||
return Inference{resultType, std::move(refinement)};
|
return Inference{resultType, std::move(refinement)};
|
||||||
}
|
}
|
||||||
case AstExprBinary::Op::Op__Count:
|
case AstExprBinary::Op::Op__Count:
|
||||||
@ -2173,7 +2222,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar
|
|||||||
|
|
||||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||||
{
|
{
|
||||||
RefinementId refinement = [&](){
|
RefinementId refinement = [&]() {
|
||||||
InConditionalContext flipper{&typeContext};
|
InConditionalContext flipper{&typeContext};
|
||||||
ScopePtr condScope = childScope(ifElse->condition, scope);
|
ScopePtr condScope = childScope(ifElse->condition, scope);
|
||||||
return check(condScope, ifElse->condition).refinement;
|
return check(condScope, ifElse->condition).refinement;
|
||||||
@ -2612,14 +2661,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||||||
LUAU_ASSERT(!indexValueLowerBound.empty());
|
LUAU_ASSERT(!indexValueLowerBound.empty());
|
||||||
|
|
||||||
TypeId indexKey = indexKeyLowerBound.size() == 1
|
TypeId indexKey = indexKeyLowerBound.size() == 1
|
||||||
? *indexKeyLowerBound.begin()
|
? *indexKeyLowerBound.begin()
|
||||||
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())})
|
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
|
||||||
;
|
|
||||||
|
|
||||||
TypeId indexValue = indexValueLowerBound.size() == 1
|
TypeId indexValue = indexValueLowerBound.size() == 1
|
||||||
? *indexValueLowerBound.begin()
|
? *indexValueLowerBound.begin()
|
||||||
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())})
|
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
|
||||||
;
|
|
||||||
|
|
||||||
ttv->indexer = TableIndexer{indexKey, indexValue};
|
ttv->indexer = TableIndexer{indexKey, indexValue};
|
||||||
}
|
}
|
||||||
@ -3236,22 +3283,26 @@ void ConstraintGenerator::reportCodeTooComplex(Location location)
|
|||||||
|
|
||||||
TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
TypeFamilyInstanceType{
|
||||||
{lhs, rhs},
|
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||||
{},
|
{lhs, rhs},
|
||||||
}, scope, location);
|
{},
|
||||||
|
},
|
||||||
|
scope, location);
|
||||||
|
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
||||||
{
|
{
|
||||||
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
|
TypeId resultType = createFamilyInstance(
|
||||||
NotNull{&kBuiltinTypeFamilies.intersectFamily},
|
TypeFamilyInstanceType{
|
||||||
{lhs, rhs},
|
NotNull{&kBuiltinTypeFamilies.intersectFamily},
|
||||||
{},
|
{lhs, rhs},
|
||||||
}, scope, location);
|
{},
|
||||||
|
},
|
||||||
|
scope, location);
|
||||||
|
|
||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
@ -3329,9 +3380,13 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
|
|||||||
scope->bindings[symbol] = Binding{tys.front(), location};
|
scope->bindings[symbol] = Binding{tys.front(), location};
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TypeId ty = arena->addType(BlockedType{});
|
TypeId ty = createFamilyInstance(
|
||||||
auto c = addConstraint(globalScope, Location{}, SetOpConstraint{SetOpConstraint::Union, ty, std::move(tys)});
|
TypeFamilyInstanceType{
|
||||||
getMutable<BlockedType>(ty)->setOwner(c);
|
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||||
|
std::move(tys),
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
globalScope, Location{});
|
||||||
|
|
||||||
scope->bindings[symbol] = Binding{ty, location};
|
scope->bindings[symbol] = Binding{ty, location};
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,8 @@ void ConstraintSolver::run()
|
|||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
{
|
{
|
||||||
printf("Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str());
|
printf(
|
||||||
|
"Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str());
|
||||||
dump(this, opts);
|
dump(this, opts);
|
||||||
printf("Bindings:\n");
|
printf("Bindings:\n");
|
||||||
dumpBindings(rootScope, opts);
|
dumpBindings(rootScope, opts);
|
||||||
@ -492,8 +493,6 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||||||
success = tryDispatch(*psc, constraint, force);
|
success = tryDispatch(*psc, constraint, force);
|
||||||
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
else if (auto gc = get<GeneralizationConstraint>(*constraint))
|
||||||
success = tryDispatch(*gc, constraint, force);
|
success = tryDispatch(*gc, constraint, force);
|
||||||
else if (auto ic = get<InstantiationConstraint>(*constraint))
|
|
||||||
success = tryDispatch(*ic, constraint, force);
|
|
||||||
else if (auto ic = get<IterableConstraint>(*constraint))
|
else if (auto ic = get<IterableConstraint>(*constraint))
|
||||||
success = tryDispatch(*ic, constraint, force);
|
success = tryDispatch(*ic, constraint, force);
|
||||||
else if (auto nc = get<NameConstraint>(*constraint))
|
else if (auto nc = get<NameConstraint>(*constraint))
|
||||||
@ -514,14 +513,10 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
|||||||
success = tryDispatch(*spc, constraint);
|
success = tryDispatch(*spc, constraint);
|
||||||
else if (auto spc = get<SetIndexerConstraint>(*constraint))
|
else if (auto spc = get<SetIndexerConstraint>(*constraint))
|
||||||
success = tryDispatch(*spc, constraint, force);
|
success = tryDispatch(*spc, constraint, force);
|
||||||
else if (auto sottc = get<SingletonOrTopTypeConstraint>(*constraint))
|
|
||||||
success = tryDispatch(*sottc, constraint);
|
|
||||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||||
success = tryDispatch(*uc, constraint);
|
success = tryDispatch(*uc, constraint);
|
||||||
else if (auto uc = get<Unpack1Constraint>(*constraint))
|
else if (auto uc = get<Unpack1Constraint>(*constraint))
|
||||||
success = tryDispatch(*uc, constraint);
|
success = tryDispatch(*uc, constraint);
|
||||||
else if (auto soc = get<SetOpConstraint>(*constraint))
|
|
||||||
success = tryDispatch(*soc, constraint, force);
|
|
||||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||||
success = tryDispatch(*rc, constraint, force);
|
success = tryDispatch(*rc, constraint, force);
|
||||||
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
||||||
@ -611,40 +606,6 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
||||||
{
|
|
||||||
if (isBlocked(c.superType))
|
|
||||||
return block(c.superType, constraint);
|
|
||||||
|
|
||||||
if (!blockOnPendingTypes(c.superType, constraint))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// TODO childLimit
|
|
||||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, NotNull{arena}, NotNull{&limits}, constraint->scope, c.superType);
|
|
||||||
|
|
||||||
LUAU_ASSERT(get<BlockedType>(c.subType));
|
|
||||||
LUAU_ASSERT(canMutate(c.subType, constraint));
|
|
||||||
|
|
||||||
if (!instantiated.has_value())
|
|
||||||
{
|
|
||||||
reportError(UnificationTooComplex{}, constraint->location);
|
|
||||||
|
|
||||||
bindBlockedType(c.subType, errorRecoveryType(), c.superType, constraint);
|
|
||||||
unblock(c.subType, constraint->location);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindBlockedType(c.subType, *instantiated, c.superType, constraint);
|
|
||||||
|
|
||||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
|
||||||
queuer.traverse(c.subType);
|
|
||||||
|
|
||||||
unblock(c.subType, constraint->location);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -936,7 +897,8 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||||||
|
|
||||||
// Type function application will happily give us the exact same type if
|
// Type function application will happily give us the exact same type if
|
||||||
// there are e.g. generic saturatedTypeArguments that go unused.
|
// there are e.g. generic saturatedTypeArguments that go unused.
|
||||||
bool needsClone = follow(tf->type) == target;
|
const TableType* tfTable = getTableType(tf->type);
|
||||||
|
bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target));
|
||||||
// Only tables have the properties we're trying to set.
|
// Only tables have the properties we're trying to set.
|
||||||
TableType* ttv = getMutableTableType(target);
|
TableType* ttv = getMutableTableType(target);
|
||||||
|
|
||||||
@ -1462,7 +1424,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen)
|
bool ConstraintSolver::tryDispatchHasIndexer(
|
||||||
|
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen)
|
||||||
{
|
{
|
||||||
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit};
|
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit};
|
||||||
|
|
||||||
@ -1481,12 +1444,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
|
|||||||
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||||
asMutable(resultType)->ty.emplace<FreeType>(freeResult);
|
asMutable(resultType)->ty.emplace<FreeType>(freeResult);
|
||||||
|
|
||||||
TypeId upperBound = arena->addType(TableType{
|
TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, TableState::Unsealed});
|
||||||
/* props */ {},
|
|
||||||
TableIndexer{indexType, resultType},
|
|
||||||
TypeLevel{},
|
|
||||||
TableState::Unsealed
|
|
||||||
});
|
|
||||||
|
|
||||||
unify(constraint, subjectType, upperBound);
|
unify(constraint, subjectType, upperBound);
|
||||||
|
|
||||||
@ -1538,12 +1496,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
|
|||||||
// ~(false | nil) <: {[indexType]: resultType}
|
// ~(false | nil) <: {[indexType]: resultType}
|
||||||
|
|
||||||
Set<TypeId> parts{nullptr};
|
Set<TypeId> parts{nullptr};
|
||||||
for (TypeId part: it)
|
for (TypeId part : it)
|
||||||
parts.insert(follow(part));
|
parts.insert(follow(part));
|
||||||
|
|
||||||
Set<TypeId> results{nullptr};
|
Set<TypeId> results{nullptr};
|
||||||
|
|
||||||
for (TypeId part: parts)
|
for (TypeId part : parts)
|
||||||
{
|
{
|
||||||
TypeId r = arena->addType(BlockedType{});
|
TypeId r = arena->addType(BlockedType{});
|
||||||
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
|
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
|
||||||
@ -1570,12 +1528,12 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull<const
|
|||||||
else if (auto ut = get<UnionType>(subjectType))
|
else if (auto ut = get<UnionType>(subjectType))
|
||||||
{
|
{
|
||||||
Set<TypeId> parts{nullptr};
|
Set<TypeId> parts{nullptr};
|
||||||
for (TypeId part: ut)
|
for (TypeId part : ut)
|
||||||
parts.insert(follow(part));
|
parts.insert(follow(part));
|
||||||
|
|
||||||
Set<TypeId> results{nullptr};
|
Set<TypeId> results{nullptr};
|
||||||
|
|
||||||
for (TypeId part: parts)
|
for (TypeId part : parts)
|
||||||
{
|
{
|
||||||
TypeId r = arena->addType(BlockedType{});
|
TypeId r = arena->addType(BlockedType{});
|
||||||
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
|
getMutable<BlockedType>(r)->setOwner(const_cast<Constraint*>(constraint.get()));
|
||||||
@ -1625,7 +1583,7 @@ struct BlockedTypeFinder : TypeOnceVisitor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint)
|
bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
@ -1651,25 +1609,24 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const
|
|||||||
return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType, seen);
|
return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<bool, bool> ConstraintSolver::tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds)
|
std::pair<bool, std::optional<TypeId>> ConstraintSolver::tryDispatchSetIndexer(
|
||||||
|
NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds)
|
||||||
{
|
{
|
||||||
if (isBlocked(subjectType))
|
if (isBlocked(subjectType))
|
||||||
return {block(subjectType, constraint), false};
|
return {block(subjectType, constraint), std::nullopt};
|
||||||
|
|
||||||
if (auto tt = getMutable<TableType>(subjectType))
|
if (auto tt = getMutable<TableType>(subjectType))
|
||||||
{
|
{
|
||||||
if (tt->indexer)
|
if (tt->indexer)
|
||||||
{
|
{
|
||||||
unify(constraint, indexType, tt->indexer->indexType);
|
unify(constraint, indexType, tt->indexer->indexType);
|
||||||
bindBlockedType(propType, tt->indexer->indexResultType, subjectType, constraint);
|
return {true, tt->indexer->indexResultType};
|
||||||
|
|
||||||
return {true, true};
|
|
||||||
}
|
}
|
||||||
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||||
{
|
{
|
||||||
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
|
TypeId resultTy = freshType(arena, builtinTypes, constraint->scope.get());
|
||||||
tt->indexer = TableIndexer{indexType, propType};
|
tt->indexer = TableIndexer{indexType, resultTy};
|
||||||
return {true, true};
|
return {true, resultTy};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto ft = getMutable<FreeType>(subjectType); ft && expandFreeTypeBounds)
|
else if (auto ft = getMutable<FreeType>(subjectType); ft && expandFreeTypeBounds)
|
||||||
@ -1678,41 +1635,51 @@ std::pair<bool, bool> ConstraintSolver::tryDispatchSetIndexer(NotNull<const Cons
|
|||||||
// Therefore, we only care about the upper bound.
|
// Therefore, we only care about the upper bound.
|
||||||
//
|
//
|
||||||
// We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer.
|
// We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer.
|
||||||
auto [dispatched, found] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/ false);
|
auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/false);
|
||||||
if (dispatched && !found)
|
if (dispatched && !resultTy)
|
||||||
{
|
{
|
||||||
// Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find.
|
// Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find.
|
||||||
found = true;
|
resultTy = freshType(arena, builtinTypes, constraint->scope.get());
|
||||||
bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
|
|
||||||
|
|
||||||
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
|
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
|
||||||
TableType* tt2 = getMutable<TableType>(tableTy);
|
TableType* tt2 = getMutable<TableType>(tableTy);
|
||||||
tt2->indexer = TableIndexer{indexType, propType};
|
tt2->indexer = TableIndexer{indexType, *resultTy};
|
||||||
|
|
||||||
ft->upperBound = simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint.
|
ft->upperBound =
|
||||||
|
simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint.
|
||||||
}
|
}
|
||||||
|
|
||||||
return {dispatched, found};
|
return {dispatched, resultTy};
|
||||||
}
|
}
|
||||||
else if (auto it = get<IntersectionType>(subjectType))
|
else if (auto it = get<IntersectionType>(subjectType))
|
||||||
{
|
{
|
||||||
std::pair<bool, bool> result{true, true};
|
bool dispatched = true;
|
||||||
|
std::vector<TypeId> results;
|
||||||
|
|
||||||
for (TypeId part : it)
|
for (TypeId part : it)
|
||||||
{
|
{
|
||||||
auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
|
auto [dispatched2, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
|
||||||
result.first &= dispatched;
|
dispatched &= dispatched2;
|
||||||
result.second &= found;
|
results.push_back(found.value_or(builtinTypes->errorRecoveryType()));
|
||||||
|
|
||||||
|
if (!dispatched)
|
||||||
|
return {dispatched, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
TypeId resultTy = arena->addType(TypeFamilyInstanceType{
|
||||||
}
|
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||||
else if (is<AnyType, ErrorType, NeverType>(subjectType) && expandFreeTypeBounds)
|
std::move(results),
|
||||||
{
|
{},
|
||||||
bindBlockedType(propType, subjectType, subjectType, constraint);
|
});
|
||||||
return {true, true};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {true, false};
|
pushConstraint(constraint->scope, constraint->location, ReduceConstraint{resultTy});
|
||||||
|
|
||||||
|
return {dispatched, resultTy};
|
||||||
|
}
|
||||||
|
else if (is<AnyType, ErrorType, NeverType>(subjectType))
|
||||||
|
return {true, subjectType};
|
||||||
|
|
||||||
|
return {true, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
@ -1721,54 +1688,38 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
|||||||
if (isBlocked(subjectType))
|
if (isBlocked(subjectType))
|
||||||
return block(subjectType, constraint);
|
return block(subjectType, constraint);
|
||||||
|
|
||||||
auto [dispatched, found] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/ true);
|
auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/true);
|
||||||
if (dispatched)
|
if (dispatched)
|
||||||
{
|
{
|
||||||
if (!found)
|
bindBlockedType(c.propType, resultTy.value_or(builtinTypes->errorRecoveryType()), subjectType, constraint);
|
||||||
bindBlockedType(c.propType, builtinTypes->errorRecoveryType(), subjectType, constraint);
|
|
||||||
|
|
||||||
unblock(c.propType, constraint->location);
|
unblock(c.propType, constraint->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dispatched;
|
return dispatched;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint)
|
|
||||||
{
|
|
||||||
if (isBlocked(c.discriminantType))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TypeId followed = follow(c.discriminantType);
|
|
||||||
|
|
||||||
// `nil` is a singleton type too! There's only one value of type `nil`.
|
|
||||||
if (c.negated && (get<SingletonType>(followed) || isNil(followed)))
|
|
||||||
*asMutable(c.resultType) = NegationType{c.discriminantType};
|
|
||||||
else if (!c.negated && get<SingletonType>(followed))
|
|
||||||
*asMutable(c.resultType) = BoundType{c.discriminantType};
|
|
||||||
else
|
|
||||||
*asMutable(c.resultType) = BoundType{builtinTypes->anyType};
|
|
||||||
|
|
||||||
unblock(c.resultType, constraint->location);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue)
|
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue)
|
||||||
{
|
{
|
||||||
resultTy = follow(resultTy);
|
resultTy = follow(resultTy);
|
||||||
LUAU_ASSERT(canMutate(resultTy, constraint));
|
LUAU_ASSERT(canMutate(resultTy, constraint));
|
||||||
|
|
||||||
if (auto lt = getMutable<LocalType>(resultTy); resultIsLValue && lt)
|
auto tryExpand = [&](TypeId ty) {
|
||||||
{
|
LocalType* lt = getMutable<LocalType>(ty);
|
||||||
|
if (!lt || !resultIsLValue)
|
||||||
|
return;
|
||||||
|
|
||||||
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
|
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
|
||||||
LUAU_ASSERT(lt->blockCount > 0);
|
LUAU_ASSERT(lt->blockCount > 0);
|
||||||
--lt->blockCount;
|
--lt->blockCount;
|
||||||
|
|
||||||
LUAU_ASSERT(0 <= lt->blockCount);
|
|
||||||
|
|
||||||
if (0 == lt->blockCount)
|
if (0 == lt->blockCount)
|
||||||
asMutable(resultTy)->ty.emplace<BoundType>(lt->domain);
|
asMutable(ty)->ty.emplace<BoundType>(lt->domain);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
if (auto ut = get<UnionType>(resultTy))
|
||||||
|
std::for_each(begin(ut), end(ut), tryExpand);
|
||||||
|
else if (get<LocalType>(resultTy))
|
||||||
|
tryExpand(resultTy);
|
||||||
else if (get<BlockedType>(resultTy))
|
else if (get<BlockedType>(resultTy))
|
||||||
{
|
{
|
||||||
if (follow(srcTy) == resultTy)
|
if (follow(srcTy) == resultTy)
|
||||||
@ -1823,21 +1774,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||||||
|
|
||||||
TypeId srcTy = follow(srcPack.head[i]);
|
TypeId srcTy = follow(srcPack.head[i]);
|
||||||
TypeId resultTy = follow(*resultIter);
|
TypeId resultTy = follow(*resultIter);
|
||||||
|
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
|
||||||
if (resultTy)
|
|
||||||
{
|
|
||||||
// when we preserve the error-suppression of types through typestate,
|
|
||||||
// we introduce a union with the error type, so we need to find the local type in those options to update.
|
|
||||||
if (auto ut = getMutable<UnionType>(resultTy))
|
|
||||||
{
|
|
||||||
for (auto opt : ut->options)
|
|
||||||
tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
unify(constraint, srcTy, resultTy);
|
|
||||||
|
|
||||||
++resultIter;
|
++resultIter;
|
||||||
++i;
|
++i;
|
||||||
@ -1877,34 +1814,6 @@ bool ConstraintSolver::tryDispatch(const Unpack1Constraint& c, NotNull<const Con
|
|||||||
return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue);
|
return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
|
|
||||||
{
|
|
||||||
bool blocked = false;
|
|
||||||
for (TypeId ty : c.types)
|
|
||||||
{
|
|
||||||
if (isBlocked(ty))
|
|
||||||
{
|
|
||||||
blocked = true;
|
|
||||||
block(ty, constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (blocked && !force)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LUAU_ASSERT(SetOpConstraint::Union == c.mode);
|
|
||||||
|
|
||||||
TypeId res = builtinTypes->neverType;
|
|
||||||
|
|
||||||
for (TypeId ty : c.types)
|
|
||||||
res = simplifyUnion(builtinTypes, arena, res, ty).result;
|
|
||||||
|
|
||||||
bindBlockedType(c.resultType, res, c.resultType, constraint);
|
|
||||||
|
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(res);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypeId ty = follow(c.ty);
|
TypeId ty = follow(c.ty);
|
||||||
@ -1917,6 +1826,20 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||||||
for (TypePackId r : result.reducedPacks)
|
for (TypePackId r : result.reducedPacks)
|
||||||
unblock(r, constraint->location);
|
unblock(r, constraint->location);
|
||||||
|
|
||||||
|
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||||
|
|
||||||
|
if (force || reductionFinished)
|
||||||
|
{
|
||||||
|
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock.
|
||||||
|
for (auto error : result.errors)
|
||||||
|
{
|
||||||
|
if (auto utf = get<UninhabitedTypeFamily>(error))
|
||||||
|
uninhabitedTypeFamilies.insert(utf->ty);
|
||||||
|
else if (auto utpf = get<UninhabitedTypePackFamily>(error))
|
||||||
|
uninhabitedTypeFamilies.insert(utpf->tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (force)
|
if (force)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1926,7 +1849,7 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||||||
for (TypePackId b : result.blockedPacks)
|
for (TypePackId b : result.blockedPacks)
|
||||||
block(b, constraint);
|
block(b, constraint);
|
||||||
|
|
||||||
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
return reductionFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
@ -1941,6 +1864,20 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
|
|||||||
for (TypePackId r : result.reducedPacks)
|
for (TypePackId r : result.reducedPacks)
|
||||||
unblock(r, constraint->location);
|
unblock(r, constraint->location);
|
||||||
|
|
||||||
|
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||||
|
|
||||||
|
if (force || reductionFinished)
|
||||||
|
{
|
||||||
|
// if we're completely dispatching this constraint, we want to record any uninhabited type families to unblock.
|
||||||
|
for (auto error : result.errors)
|
||||||
|
{
|
||||||
|
if (auto utf = get<UninhabitedTypeFamily>(error))
|
||||||
|
uninhabitedTypeFamilies.insert(utf->ty);
|
||||||
|
else if (auto utpf = get<UninhabitedTypePackFamily>(error))
|
||||||
|
uninhabitedTypeFamilies.insert(utpf->tp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (force)
|
if (force)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -1950,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
|
|||||||
for (TypePackId b : result.blockedPacks)
|
for (TypePackId b : result.blockedPacks)
|
||||||
block(b, constraint);
|
block(b, constraint);
|
||||||
|
|
||||||
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
return reductionFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
@ -2075,7 +2012,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
|||||||
LUAU_ASSERT(nextFn);
|
LUAU_ASSERT(nextFn);
|
||||||
const TypePackId nextRetPack = nextFn->retTypes;
|
const TypePackId nextRetPack = nextFn->retTypes;
|
||||||
|
|
||||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/ true});
|
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2405,7 +2342,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
|||||||
template<typename TID>
|
template<typename TID>
|
||||||
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||||
{
|
{
|
||||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFamilies};
|
||||||
|
|
||||||
const bool ok = u2.unify(subTy, superTy);
|
const bool ok = u2.unify(subTy, superTy);
|
||||||
|
|
||||||
@ -2672,12 +2609,20 @@ bool ConstraintSolver::isBlocked(TypeId ty)
|
|||||||
if (auto lt = get<LocalType>(ty))
|
if (auto lt = get<LocalType>(ty))
|
||||||
return lt->blockCount > 0;
|
return lt->blockCount > 0;
|
||||||
|
|
||||||
|
if (auto tfit = get<TypeFamilyInstanceType>(ty))
|
||||||
|
return uninhabitedTypeFamilies.contains(ty) == false;
|
||||||
|
|
||||||
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
|
return nullptr != get<BlockedType>(ty) || nullptr != get<PendingExpansionType>(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::isBlocked(TypePackId tp)
|
bool ConstraintSolver::isBlocked(TypePackId tp)
|
||||||
{
|
{
|
||||||
return nullptr != get<BlockedTypePack>(follow(tp));
|
tp = follow(tp);
|
||||||
|
|
||||||
|
if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
|
||||||
|
return uninhabitedTypeFamilies.contains(tp) == false;
|
||||||
|
|
||||||
|
return nullptr != get<BlockedTypePack>(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauImproveNonFunctionCallError, false)
|
||||||
|
|
||||||
static std::string wrongNumberOfArgsString(
|
static std::string wrongNumberOfArgsString(
|
||||||
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
|
||||||
{
|
{
|
||||||
@ -335,8 +337,65 @@ struct ErrorConverter
|
|||||||
return e.message;
|
return e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<TypeId> findCallMetamethod(TypeId type) const
|
||||||
|
{
|
||||||
|
type = follow(type);
|
||||||
|
|
||||||
|
std::optional<TypeId> metatable;
|
||||||
|
if (const MetatableType* mtType = get<MetatableType>(type))
|
||||||
|
metatable = mtType->metatable;
|
||||||
|
else if (const ClassType* classType = get<ClassType>(type))
|
||||||
|
metatable = classType->metatable;
|
||||||
|
|
||||||
|
if (!metatable)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
TypeId unwrapped = follow(*metatable);
|
||||||
|
|
||||||
|
if (get<AnyType>(unwrapped))
|
||||||
|
return unwrapped;
|
||||||
|
|
||||||
|
const TableType* mtt = getTableType(unwrapped);
|
||||||
|
if (!mtt)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
auto it = mtt->props.find("__call");
|
||||||
|
if (it != mtt->props.end())
|
||||||
|
return it->second.type();
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::string operator()(const Luau::CannotCallNonFunction& e) const
|
std::string operator()(const Luau::CannotCallNonFunction& e) const
|
||||||
{
|
{
|
||||||
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
|
{
|
||||||
|
if (auto unionTy = get<UnionType>(follow(e.ty)))
|
||||||
|
{
|
||||||
|
std::string err = "Cannot call a value of the union type:";
|
||||||
|
|
||||||
|
for (auto option : unionTy)
|
||||||
|
{
|
||||||
|
option = follow(option);
|
||||||
|
|
||||||
|
if (get<FunctionType>(option) || findCallMetamethod(option))
|
||||||
|
{
|
||||||
|
err += "\n | " + toString(option);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// early-exit if we find something that isn't callable in the union.
|
||||||
|
return "Cannot call a value of type " + toString(option) + " in union:\n " + toString(e.ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
err += "\nWe are unable to determine the appropriate result type for such a call.";
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Cannot call a value of type " + toString(e.ty);
|
||||||
|
}
|
||||||
|
|
||||||
return "Cannot call non-function " + toString(e.ty);
|
return "Cannot call non-function " + toString(e.ty);
|
||||||
}
|
}
|
||||||
std::string operator()(const Luau::ExtraInformation& e) const
|
std::string operator()(const Luau::ExtraInformation& e) const
|
||||||
|
@ -31,9 +31,7 @@ TypeId Instantiation2::clean(TypeId ty)
|
|||||||
|
|
||||||
// if we didn't learn anything about the lower bound, we pick the upper bound instead.
|
// if we didn't learn anything about the lower bound, we pick the upper bound instead.
|
||||||
// we default to the lower bound which represents the most specific type for the free type.
|
// we default to the lower bound which represents the most specific type for the free type.
|
||||||
TypeId res = get<NeverType>(ft->lowerBound)
|
TypeId res = get<NeverType>(ft->lowerBound) ? ft->upperBound : ft->lowerBound;
|
||||||
? ft->upperBound
|
|
||||||
: ft->lowerBound;
|
|
||||||
|
|
||||||
// Instantiation should not traverse into the type that we are substituting for.
|
// Instantiation should not traverse into the type that we are substituting for.
|
||||||
dontTraverseInto(res);
|
dontTraverseInto(res);
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
|
LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauNormalizeNotUnknownIntersection, false);
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixCyclicUnionsOfIntersections, false);
|
||||||
|
|
||||||
// This could theoretically be 2000 on amd64, but x86 requires this.
|
// This could theoretically be 2000 on amd64, but x86 requires this.
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||||
@ -29,6 +31,11 @@ static bool fixNormalizeCaching()
|
|||||||
return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution;
|
return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool fixCyclicUnionsOfIntersections()
|
||||||
|
{
|
||||||
|
return FFlag::LuauFixCyclicUnionsOfIntersections || FFlag::DebugLuauDeferredConstraintResolution;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -910,13 +917,13 @@ static bool isCacheable(TypeId ty, Set<TypeId>& seen)
|
|||||||
|
|
||||||
if (auto tfi = get<TypeFamilyInstanceType>(ty))
|
if (auto tfi = get<TypeFamilyInstanceType>(ty))
|
||||||
{
|
{
|
||||||
for (TypeId t: tfi->typeArguments)
|
for (TypeId t : tfi->typeArguments)
|
||||||
{
|
{
|
||||||
if (!isCacheable(t, seen))
|
if (!isCacheable(t, seen))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TypePackId tp: tfi->packArguments)
|
for (TypePackId tp : tfi->packArguments)
|
||||||
{
|
{
|
||||||
if (!isCacheable(tp, seen))
|
if (!isCacheable(tp, seen))
|
||||||
return false;
|
return false;
|
||||||
@ -1768,14 +1775,29 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t
|
|||||||
}
|
}
|
||||||
else if (const IntersectionType* itv = get<IntersectionType>(there))
|
else if (const IntersectionType* itv = get<IntersectionType>(there))
|
||||||
{
|
{
|
||||||
|
if (fixCyclicUnionsOfIntersections())
|
||||||
|
{
|
||||||
|
if (seenSetTypes.count(there))
|
||||||
|
return NormalizationResult::True;
|
||||||
|
seenSetTypes.insert(there);
|
||||||
|
}
|
||||||
|
|
||||||
NormalizedType norm{builtinTypes};
|
NormalizedType norm{builtinTypes};
|
||||||
norm.tops = builtinTypes->anyType;
|
norm.tops = builtinTypes->anyType;
|
||||||
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
|
for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it)
|
||||||
{
|
{
|
||||||
NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes);
|
NormalizationResult res = intersectNormalWithTy(norm, *it, seenSetTypes);
|
||||||
if (res != NormalizationResult::True)
|
if (res != NormalizationResult::True)
|
||||||
|
{
|
||||||
|
if (fixCyclicUnionsOfIntersections())
|
||||||
|
seenSetTypes.erase(there);
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fixCyclicUnionsOfIntersections())
|
||||||
|
seenSetTypes.erase(there);
|
||||||
|
|
||||||
return unionNormals(here, norm);
|
return unionNormals(here, norm);
|
||||||
}
|
}
|
||||||
else if (get<UnknownType>(here.tops))
|
else if (get<UnknownType>(here.tops))
|
||||||
@ -3194,6 +3216,13 @@ NormalizationResult Normalizer::intersectNormalWithTy(NormalizedType& here, Type
|
|||||||
// this is a noop since an intersection with `unknown` is trivial.
|
// this is a noop since an intersection with `unknown` is trivial.
|
||||||
return NormalizationResult::True;
|
return NormalizationResult::True;
|
||||||
}
|
}
|
||||||
|
else if ((FFlag::LuauNormalizeNotUnknownIntersection || FFlag::DebugLuauDeferredConstraintResolution) && get<UnknownType>(t))
|
||||||
|
{
|
||||||
|
// if we're intersecting with `~unknown`, this is equivalent to intersecting with `never`
|
||||||
|
// this means we should clear the type entirely.
|
||||||
|
clearNormal(here);
|
||||||
|
return NormalizationResult::True;
|
||||||
|
}
|
||||||
else if (auto nt = get<NegationType>(t))
|
else if (auto nt = get<NegationType>(t))
|
||||||
return intersectNormalWithTy(here, nt->ty, seenSetTypes);
|
return intersectNormalWithTy(here, nt->ty, seenSetTypes);
|
||||||
else
|
else
|
||||||
|
@ -365,41 +365,25 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
|
|||||||
|
|
||||||
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
|
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
|
||||||
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
|
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
|
||||||
std::optional<TypeId> selectOverload(
|
std::optional<TypeId> selectOverload(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<Normalizer> normalizer,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
NotNull<Scope> scope, NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, const Location& location, TypeId fn,
|
||||||
NotNull<TypeArena> arena,
|
TypePackId argsPack)
|
||||||
NotNull<Normalizer> normalizer,
|
|
||||||
NotNull<Scope> scope,
|
|
||||||
NotNull<InternalErrorReporter> iceReporter,
|
|
||||||
NotNull<TypeCheckLimits> limits,
|
|
||||||
const Location& location,
|
|
||||||
TypeId fn,
|
|
||||||
TypePackId argsPack
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
|
OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
|
||||||
auto [status, overload] = resolver.selectOverload(fn, argsPack);
|
auto [status, overload] = resolver.selectOverload(fn, argsPack);
|
||||||
|
|
||||||
if (status == OverloadResolver::Analysis::Ok)
|
if (status == OverloadResolver::Analysis::Ok)
|
||||||
return overload;
|
return overload;
|
||||||
|
|
||||||
if (get<AnyType>(fn) || get<FreeType>(fn))
|
if (get<AnyType>(fn) || get<FreeType>(fn))
|
||||||
return fn;
|
return fn;
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
SolveResult solveFunctionCall(
|
SolveResult solveFunctionCall(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Normalizer> normalizer,
|
||||||
NotNull<TypeArena> arena,
|
NotNull<InternalErrorReporter> iceReporter, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, const Location& location, TypeId fn,
|
||||||
NotNull<BuiltinTypes> builtinTypes,
|
TypePackId argsPack)
|
||||||
NotNull<Normalizer> normalizer,
|
|
||||||
NotNull<InternalErrorReporter> iceReporter,
|
|
||||||
NotNull<TypeCheckLimits> limits,
|
|
||||||
NotNull<Scope> scope,
|
|
||||||
const Location& location,
|
|
||||||
TypeId fn,
|
|
||||||
TypePackId argsPack
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack);
|
std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack);
|
||||||
if (!overloadToUse)
|
if (!overloadToUse)
|
||||||
|
@ -266,7 +266,8 @@ struct ApplyMappedGenerics : Substitution
|
|||||||
MappedGenericPacks& mappedGenericPacks;
|
MappedGenericPacks& mappedGenericPacks;
|
||||||
|
|
||||||
|
|
||||||
ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks)
|
ApplyMappedGenerics(
|
||||||
|
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks)
|
||||||
: Substitution(TxnLog::empty(), arena)
|
: Substitution(TxnLog::empty(), arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
, arena(arena)
|
, arena(arena)
|
||||||
@ -1244,18 +1245,18 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
|||||||
{
|
{
|
||||||
if (superProp.isShared())
|
if (superProp.isShared())
|
||||||
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
|
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
|
||||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||||
.withSuperComponent(TypePath::Property::read(name)));
|
.withSuperComponent(TypePath::Property::read(name)));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (superProp.readTy)
|
if (superProp.readTy)
|
||||||
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
|
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
|
||||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||||
.withSuperComponent(TypePath::Property::read(name)));
|
.withSuperComponent(TypePath::Property::read(name)));
|
||||||
if (superProp.writeTy)
|
if (superProp.writeTy)
|
||||||
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
|
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
|
||||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||||
.withSuperComponent(TypePath::Property::write(name)));
|
.withSuperComponent(TypePath::Property::write(name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1310,7 +1311,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
|
|||||||
return {isSubclass(subClass, superClass)};
|
return {isSubclass(subClass, superClass)};
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable)
|
SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable)
|
||||||
{
|
{
|
||||||
SubtypingResult result{true};
|
SubtypingResult result{true};
|
||||||
|
|
||||||
@ -1421,7 +1423,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prop
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm)
|
SubtypingResult Subtyping::isCovariantWith(
|
||||||
|
SubtypingEnvironment& env, const std::shared_ptr<const NormalizedType>& subNorm, const std::shared_ptr<const NormalizedType>& superNorm)
|
||||||
{
|
{
|
||||||
if (!subNorm || !superNorm)
|
if (!subNorm || !superNorm)
|
||||||
return {false, true};
|
return {false, true};
|
||||||
@ -1584,15 +1587,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type
|
|||||||
{
|
{
|
||||||
// Reduce the typefamily instance
|
// Reduce the typefamily instance
|
||||||
auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance);
|
auto [ty, errors] = handleTypeFamilyReductionResult(subFamilyInstance);
|
||||||
|
|
||||||
// If we return optional, that means the type family was irreducible - we can reduce that to never
|
// If we return optional, that means the type family was irreducible - we can reduce that to never
|
||||||
return isCovariantWith(env, ty, superTy).withErrors(errors);
|
return isCovariantWith(env, ty, superTy).withErrors(errors).withSubComponent(TypePath::Reduction{ty});
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance)
|
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const TypeFamilyInstanceType* superFamilyInstance)
|
||||||
{
|
{
|
||||||
// Reduce the typefamily instance
|
// Reduce the typefamily instance
|
||||||
auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance);
|
auto [ty, errors] = handleTypeFamilyReductionResult(superFamilyInstance);
|
||||||
return isCovariantWith(env, subTy, ty).withErrors(errors);
|
return isCovariantWith(env, subTy, ty).withErrors(errors).withSuperComponent(TypePath::Reduction{ty});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -13,14 +13,8 @@ namespace Luau
|
|||||||
|
|
||||||
static bool isLiteral(const AstExpr* expr)
|
static bool isLiteral(const AstExpr* expr)
|
||||||
{
|
{
|
||||||
return (
|
return (expr->is<AstExprTable>() || expr->is<AstExprFunction>() || expr->is<AstExprConstantNumber>() || expr->is<AstExprConstantString>() ||
|
||||||
expr->is<AstExprTable>() ||
|
expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNil>());
|
||||||
expr->is<AstExprFunction>() ||
|
|
||||||
expr->is<AstExprConstantNumber>() ||
|
|
||||||
expr->is<AstExprConstantString>() ||
|
|
||||||
expr->is<AstExprConstantBool>() ||
|
|
||||||
expr->is<AstExprConstantNil>()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A fast approximation of subTy <: superTy
|
// A fast approximation of subTy <: superTy
|
||||||
@ -52,7 +46,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
|
|||||||
size_t tableCount = 0;
|
size_t tableCount = 0;
|
||||||
std::optional<TypeId> firstTable;
|
std::optional<TypeId> firstTable;
|
||||||
|
|
||||||
for (TypeId ty: tables)
|
for (TypeId ty : tables)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
if (auto tt = get<TableType>(ty))
|
if (auto tt = get<TableType>(ty))
|
||||||
@ -65,7 +59,7 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
|
|||||||
firstTable = ty;
|
firstTable = ty;
|
||||||
++tableCount;
|
++tableCount;
|
||||||
|
|
||||||
for (const auto& [name, expectedProp]: tt->props)
|
for (const auto& [name, expectedProp] : tt->props)
|
||||||
{
|
{
|
||||||
if (!expectedProp.readTy)
|
if (!expectedProp.readTy)
|
||||||
continue;
|
continue;
|
||||||
@ -91,14 +85,12 @@ static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& table
|
|||||||
|
|
||||||
if (ft && get<SingletonType>(ft->lowerBound))
|
if (ft && get<SingletonType>(ft->lowerBound))
|
||||||
{
|
{
|
||||||
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
|
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType))
|
||||||
fastIsSubtype(expectedType, builtinTypes->booleanType))
|
|
||||||
{
|
{
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
|
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound))
|
||||||
fastIsSubtype(expectedType, ft->lowerBound))
|
|
||||||
{
|
{
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
@ -149,11 +141,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
if (expr->is<AstExprConstantString>())
|
if (expr->is<AstExprConstantString>())
|
||||||
{
|
{
|
||||||
auto ft = get<FreeType>(exprType);
|
auto ft = get<FreeType>(exprType);
|
||||||
if (ft &&
|
if (ft && get<SingletonType>(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
|
||||||
get<SingletonType>(ft->lowerBound) &&
|
fastIsSubtype(ft->lowerBound, builtinTypes->stringType))
|
||||||
fastIsSubtype(builtinTypes->stringType, ft->upperBound) &&
|
|
||||||
fastIsSubtype(ft->lowerBound, builtinTypes->stringType)
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// if the upper bound is a subtype of the expected type, we can push the expected type in
|
// if the upper bound is a subtype of the expected type, we can push the expected type in
|
||||||
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
|
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
|
||||||
@ -177,11 +166,8 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
else if (expr->is<AstExprConstantBool>())
|
else if (expr->is<AstExprConstantBool>())
|
||||||
{
|
{
|
||||||
auto ft = get<FreeType>(exprType);
|
auto ft = get<FreeType>(exprType);
|
||||||
if (ft &&
|
if (ft && get<SingletonType>(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
|
||||||
get<SingletonType>(ft->lowerBound) &&
|
fastIsSubtype(ft->lowerBound, builtinTypes->booleanType))
|
||||||
fastIsSubtype(builtinTypes->booleanType, ft->upperBound) &&
|
|
||||||
fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// if the upper bound is a subtype of the expected type, we can push the expected type in
|
// if the upper bound is a subtype of the expected type, we can push the expected type in
|
||||||
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
|
Relation upperBoundRelation = relate(ft->upperBound, expectedType);
|
||||||
@ -247,7 +233,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
return exprType;
|
return exprType;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const AstExprTable::Item& item: exprTable->items)
|
for (const AstExprTable::Item& item : exprTable->items)
|
||||||
{
|
{
|
||||||
if (isRecord(item))
|
if (isRecord(item))
|
||||||
{
|
{
|
||||||
@ -391,7 +377,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
for (const auto& [name, _] : expectedTableTy->props)
|
for (const auto& [name, _] : expectedTableTy->props)
|
||||||
missingKeys.insert(name);
|
missingKeys.insert(name);
|
||||||
|
|
||||||
for (const AstExprTable::Item& item: exprTable->items)
|
for (const AstExprTable::Item& item : exprTable->items)
|
||||||
{
|
{
|
||||||
if (item.key)
|
if (item.key)
|
||||||
{
|
{
|
||||||
@ -402,7 +388,7 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& key: missingKeys)
|
for (const auto& key : missingKeys)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(key.has_value());
|
LUAU_ASSERT(key.has_value());
|
||||||
|
|
||||||
@ -427,4 +413,4 @@ TypeId matchLiteralType(NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
|
|||||||
return exprType;
|
return exprType;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Luau
|
||||||
|
@ -1767,12 +1767,6 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||||||
std::string superStr = tos(c.sourceType);
|
std::string superStr = tos(c.sourceType);
|
||||||
return subStr + " ~ gen " + superStr;
|
return subStr + " ~ gen " + superStr;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, InstantiationConstraint>)
|
|
||||||
{
|
|
||||||
std::string subStr = tos(c.subType);
|
|
||||||
std::string superStr = tos(c.superType);
|
|
||||||
return subStr + " ~ inst " + superStr;
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, IterableConstraint>)
|
else if constexpr (std::is_same_v<T, IterableConstraint>)
|
||||||
{
|
{
|
||||||
std::string iteratorStr = tos(c.iterator);
|
std::string iteratorStr = tos(c.iterator);
|
||||||
@ -1822,37 +1816,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||||||
{
|
{
|
||||||
return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
|
return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, SingletonOrTopTypeConstraint>)
|
|
||||||
{
|
|
||||||
std::string result = tos(c.resultType);
|
|
||||||
std::string discriminant = tos(c.discriminantType);
|
|
||||||
|
|
||||||
if (c.negated)
|
|
||||||
return result + " ~ if isSingleton D then ~D else unknown where D = " + discriminant;
|
|
||||||
else
|
|
||||||
return result + " ~ if isSingleton D then D else unknown where D = " + discriminant;
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||||
return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack);
|
return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack);
|
||||||
else if constexpr (std::is_same_v<T, Unpack1Constraint>)
|
else if constexpr (std::is_same_v<T, Unpack1Constraint>)
|
||||||
return tos(c.resultType) + " ~ unpack " + tos(c.sourceType);
|
return tos(c.resultType) + " ~ unpack " + tos(c.sourceType);
|
||||||
else if constexpr (std::is_same_v<T, SetOpConstraint>)
|
|
||||||
{
|
|
||||||
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";
|
|
||||||
std::string res = tos(c.resultType) + " ~ ";
|
|
||||||
bool first = true;
|
|
||||||
for (TypeId t : c.types)
|
|
||||||
{
|
|
||||||
if (first)
|
|
||||||
first = false;
|
|
||||||
else
|
|
||||||
res += op;
|
|
||||||
|
|
||||||
res += tos(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, ReduceConstraint>)
|
else if constexpr (std::is_same_v<T, ReduceConstraint>)
|
||||||
return "reduce " + tos(c.ty);
|
return "reduce " + tos(c.ty);
|
||||||
else if constexpr (std::is_same_v<T, ReducePackConstraint>)
|
else if constexpr (std::is_same_v<T, ReducePackConstraint>)
|
||||||
@ -1923,7 +1890,7 @@ std::string toString(const Position& position)
|
|||||||
std::string toString(const Location& location, int offset, bool useBegin)
|
std::string toString(const Location& location, int offset, bool useBegin)
|
||||||
{
|
{
|
||||||
return "(" + std::to_string(location.begin.line + offset) + ", " + std::to_string(location.begin.column + offset) + ") - (" +
|
return "(" + std::to_string(location.begin.line + offset) + ", " + std::to_string(location.begin.column + offset) + ") - (" +
|
||||||
std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")";
|
std::to_string(location.end.line + offset) + ", " + std::to_string(location.end.column + offset) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string toString(const TypeOrPack& tyOrTp, ToStringOptions& opts)
|
std::string toString(const TypeOrPack& tyOrTp, ToStringOptions& opts)
|
||||||
|
@ -546,11 +546,13 @@ BlockedType::BlockedType()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint* BlockedType::getOwner() const {
|
Constraint* BlockedType::getOwner() const
|
||||||
|
{
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockedType::setOwner(Constraint* newOwner) {
|
void BlockedType::setOwner(Constraint* newOwner)
|
||||||
|
{
|
||||||
LUAU_ASSERT(owner == nullptr);
|
LUAU_ASSERT(owner == nullptr);
|
||||||
|
|
||||||
if (owner != nullptr)
|
if (owner != nullptr)
|
||||||
|
@ -94,6 +94,26 @@ TypePackId TypeArena::addTypePack(TypePackVar tp)
|
|||||||
return allocated;
|
return allocated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::initializer_list<TypeId> types)
|
||||||
|
{
|
||||||
|
return addType(TypeFamilyInstanceType{family, std::move(types)});
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId TypeArena::addTypeFamily(const TypeFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||||
|
{
|
||||||
|
return addType(TypeFamilyInstanceType{family, std::move(typeArguments), std::move(packArguments)});
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::initializer_list<TypeId> types)
|
||||||
|
{
|
||||||
|
return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(types)});
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePackId TypeArena::addTypePackFamily(const TypePackFamily& family, std::vector<TypeId> typeArguments, std::vector<TypePackId> packArguments)
|
||||||
|
{
|
||||||
|
return addTypePack(TypeFamilyInstanceTypePack{NotNull{&family}, std::move(typeArguments), std::move(packArguments)});
|
||||||
|
}
|
||||||
|
|
||||||
void freeze(TypeArena& arena)
|
void freeze(TypeArena& arena)
|
||||||
{
|
{
|
||||||
if (!FFlag::DebugLuauFreezeArena)
|
if (!FFlag::DebugLuauFreezeArena)
|
||||||
|
@ -441,8 +441,8 @@ struct TypeChecker2
|
|||||||
return instance;
|
return instance;
|
||||||
seenTypeFamilyInstances.insert(instance);
|
seenTypeFamilyInstances.insert(instance);
|
||||||
|
|
||||||
ErrorVec errors = reduceFamilies(
|
ErrorVec errors = reduceFamilies(instance, location,
|
||||||
instance, location, TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
TypeFamilyContext{NotNull{&module->internalTypes}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
||||||
.errors;
|
.errors;
|
||||||
if (!isErrorSuppressing(location, instance))
|
if (!isErrorSuppressing(location, instance))
|
||||||
reportErrors(std::move(errors));
|
reportErrors(std::move(errors));
|
||||||
@ -2743,7 +2743,7 @@ struct TypeChecker2
|
|||||||
fetch(module->internalTypes.addType(IntersectionType{{tyvar, ty}}));
|
fetch(module->internalTypes.addType(IntersectionType{{tyvar, ty}}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
fetch(tyvar);
|
fetch(follow(tyvar));
|
||||||
|
|
||||||
if (!normValid)
|
if (!normValid)
|
||||||
break;
|
break;
|
||||||
@ -2871,17 +2871,24 @@ struct TypeChecker2
|
|||||||
for (TypeId part : utv)
|
for (TypeId part : utv)
|
||||||
{
|
{
|
||||||
PropertyType result = hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
|
PropertyType result = hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
|
||||||
|
|
||||||
if (result.present != NormalizationResult::True)
|
if (result.present != NormalizationResult::True)
|
||||||
return {result.present, {}};
|
return {result.present, {}};
|
||||||
if (result.result)
|
if (result.result)
|
||||||
parts.emplace_back(*result.result);
|
parts.emplace_back(*result.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts.size() == 0)
|
||||||
|
return {NormalizationResult::False, {}};
|
||||||
|
|
||||||
|
if (parts.size() == 1)
|
||||||
|
return {NormalizationResult::True, {parts[0]}};
|
||||||
|
|
||||||
TypeId propTy;
|
TypeId propTy;
|
||||||
if (context == ValueContext::LValue)
|
if (context == ValueContext::LValue)
|
||||||
module->internalTypes.addType(IntersectionType{parts});
|
propTy = module->internalTypes.addType(IntersectionType{parts});
|
||||||
else
|
else
|
||||||
module->internalTypes.addType(UnionType{parts});
|
propTy = module->internalTypes.addType(UnionType{parts});
|
||||||
|
|
||||||
return {NormalizationResult::True, propTy};
|
return {NormalizationResult::True, propTy};
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,8 @@ struct FamilyReducer
|
|||||||
else if (!reduction.uninhabited && !force)
|
else if (!reduction.uninhabited && !force)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(), reduction.blockedPacks.size());
|
printf("%s is irreducible; blocked on %zu types, %zu packs\n", toString(subject, {true}).c_str(), reduction.blockedTypes.size(),
|
||||||
|
reduction.blockedPacks.size());
|
||||||
|
|
||||||
for (TypeId b : reduction.blockedTypes)
|
for (TypeId b : reduction.blockedTypes)
|
||||||
result.blockedTypes.insert(b);
|
result.blockedTypes.insert(b);
|
||||||
@ -243,7 +244,7 @@ struct FamilyReducer
|
|||||||
if (skip == SkipTestResult::Irreducible)
|
if (skip == SkipTestResult::Irreducible)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||||
|
|
||||||
irreducible.insert(subject);
|
irreducible.insert(subject);
|
||||||
return false;
|
return false;
|
||||||
@ -251,7 +252,7 @@ struct FamilyReducer
|
|||||||
else if (skip == SkipTestResult::Defer)
|
else if (skip == SkipTestResult::Defer)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, TypeId>)
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
queuedTys.push_back(subject);
|
queuedTys.push_back(subject);
|
||||||
@ -269,7 +270,7 @@ struct FamilyReducer
|
|||||||
if (skip == SkipTestResult::Irreducible)
|
if (skip == SkipTestResult::Irreducible)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("%s is irreducible due to a dependency on %s\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
printf("%s is irreducible due to a dependency on %s\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||||
|
|
||||||
irreducible.insert(subject);
|
irreducible.insert(subject);
|
||||||
return false;
|
return false;
|
||||||
@ -277,7 +278,7 @@ struct FamilyReducer
|
|||||||
else if (skip == SkipTestResult::Defer)
|
else if (skip == SkipTestResult::Defer)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
printf("Deferring %s until %s is solved\n" , toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
printf("Deferring %s until %s is solved\n", toString(subject, {true}).c_str(), toString(p, {true}).c_str());
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, TypeId>)
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
queuedTys.push_back(subject);
|
queuedTys.push_back(subject);
|
||||||
@ -346,7 +347,8 @@ struct FamilyReducer
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
||||||
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
TypeFamilyReductionResult<TypeId> result =
|
||||||
|
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||||
handleFamilyReduction(subject, result);
|
handleFamilyReduction(subject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +373,8 @@ struct FamilyReducer
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
||||||
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
TypeFamilyReductionResult<TypePackId> result =
|
||||||
|
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||||
handleFamilyReduction(subject, result);
|
handleFamilyReduction(subject, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,8 +388,8 @@ struct FamilyReducer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static FamilyGraphReductionResult reduceFamiliesInternal(
|
static FamilyGraphReductionResult reduceFamiliesInternal(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess,
|
||||||
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
|
std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
|
||||||
{
|
{
|
||||||
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
|
||||||
int iterationCount = 0;
|
int iterationCount = 0;
|
||||||
@ -422,7 +425,8 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location,
|
|||||||
if (collector.tys.empty() && collector.tps.empty())
|
if (collector.tys.empty() && collector.tps.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force);
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
|
||||||
|
std::move(collector.cyclicInstance), location, ctx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
|
||||||
@ -441,7 +445,8 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
|
|||||||
if (collector.tys.empty() && collector.tps.empty())
|
if (collector.tys.empty() && collector.tps.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force);
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess),
|
||||||
|
std::move(collector.cyclicInstance), location, ctx, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeFamilyQueue::add(TypeId instanceTy)
|
void TypeFamilyQueue::add(TypeId instanceTy)
|
||||||
@ -461,8 +466,8 @@ bool isPending(TypeId ty, ConstraintSolver* solver)
|
|||||||
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> notFamilyFn(
|
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 1 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -479,8 +484,8 @@ TypeFamilyReductionResult<TypeId> notFamilyFn(
|
|||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> lenFamilyFn(
|
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 1 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -496,17 +501,18 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
|
|||||||
return {std::nullopt, false, {operandTy}, {}};
|
return {std::nullopt, false, {operandTy}, {}};
|
||||||
|
|
||||||
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
|
||||||
|
NormalizationResult inhabited = ctx->normalizer->isInhabited(normTy.get());
|
||||||
|
|
||||||
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
|
// if the type failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||||
if (!normTy)
|
if (!normTy || inhabited == NormalizationResult::HitLimits)
|
||||||
return {std::nullopt, false, {}, {}};
|
return {std::nullopt, false, {}, {}};
|
||||||
|
|
||||||
// if the operand type is error suppressing, we can immediately reduce to `number`.
|
// if the operand type is error suppressing, we can immediately reduce to `number`.
|
||||||
if (normTy->shouldSuppressErrors())
|
if (normTy->shouldSuppressErrors())
|
||||||
return {ctx->builtins->numberType, false, {}, {}};
|
return {ctx->builtins->numberType, false, {}, {}};
|
||||||
|
|
||||||
// if we have a `never`, we can never observe that the operator didn't work.
|
// if we have an uninhabited type (like `never`), we can never observe that the operator didn't work.
|
||||||
if (is<NeverType>(operandTy))
|
if (inhabited == NormalizationResult::False)
|
||||||
return {ctx->builtins->neverType, false, {}, {}};
|
return {ctx->builtins->neverType, false, {}, {}};
|
||||||
|
|
||||||
// if we're checking the length of a string, that works!
|
// if we're checking the length of a string, that works!
|
||||||
@ -555,8 +561,8 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(
|
|||||||
return {ctx->builtins->numberType, false, {}, {}};
|
return {ctx->builtins->numberType, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> unmFamilyFn(
|
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 1 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -732,25 +738,18 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
|||||||
// though there exists no arm of the union that is inhabited or have a reduced type.
|
// though there exists no arm of the union that is inhabited or have a reduced type.
|
||||||
ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?");
|
ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?");
|
||||||
}
|
}
|
||||||
else if (results.size() == 1)
|
|
||||||
return {results[0], false, {}, {}};
|
|
||||||
else if (results.size() == 2)
|
|
||||||
{
|
|
||||||
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
|
||||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
|
||||||
std::move(results),
|
|
||||||
{},
|
|
||||||
});
|
|
||||||
|
|
||||||
queue->add(resultTy);
|
if (results.size() == 1)
|
||||||
return {resultTy, false, {}, {}};
|
return {results[0], false, {}, {}};
|
||||||
}
|
|
||||||
else
|
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
||||||
{
|
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||||
// TODO: We need to generalize `union<...>` type family to be variadic.
|
std::move(results),
|
||||||
TypeId resultTy = ctx->arena->addType(UnionType{std::move(results)});
|
{},
|
||||||
return {resultTy, false, {}, {}};
|
});
|
||||||
}
|
|
||||||
|
queue->add(resultTy);
|
||||||
|
return {resultTy, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||||
@ -794,8 +793,8 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
|||||||
return {extracted.head.front(), false, {}, {}};
|
return {extracted.head.front(), false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> addFamilyFn(
|
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -806,8 +805,8 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> subFamilyFn(
|
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -818,8 +817,8 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> mulFamilyFn(
|
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -830,8 +829,8 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> divFamilyFn(
|
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -842,8 +841,8 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> idivFamilyFn(
|
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -854,8 +853,8 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> powFamilyFn(
|
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -866,8 +865,8 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> modFamilyFn(
|
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -878,8 +877,8 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(
|
|||||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod");
|
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> concatFamilyFn(
|
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -964,8 +963,8 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(
|
|||||||
return {ctx->builtins->stringType, false, {}, {}};
|
return {ctx->builtins->stringType, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> andFamilyFn(
|
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1001,8 +1000,8 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(
|
|||||||
return {overallResult.result, false, std::move(blockedTypes), {}};
|
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> orFamilyFn(
|
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1093,17 +1092,19 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
|
|||||||
|
|
||||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
|
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
||||||
|
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
|
||||||
|
|
||||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||||
if (!normLhsTy || !normRhsTy)
|
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
|
||||||
return {std::nullopt, false, {}, {}};
|
return {std::nullopt, false, {}, {}};
|
||||||
|
|
||||||
// if one of the types is error suppressing, we can just go ahead and reduce.
|
// if one of the types is error suppressing, we can just go ahead and reduce.
|
||||||
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
|
|
||||||
// if we have a `never`, we can never observe that the comparison didn't work.
|
// if we have an uninhabited type (e.g. `never`), we can never observe that the comparison didn't work.
|
||||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
|
||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
|
|
||||||
// If both types are some strict subset of `string`, we can reduce now.
|
// If both types are some strict subset of `string`, we can reduce now.
|
||||||
@ -1153,8 +1154,8 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
|
|||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> ltFamilyFn(
|
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1165,8 +1166,8 @@ TypeFamilyReductionResult<TypeId> ltFamilyFn(
|
|||||||
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt");
|
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> leFamilyFn(
|
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1177,8 +1178,8 @@ TypeFamilyReductionResult<TypeId> leFamilyFn(
|
|||||||
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le");
|
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le");
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> eqFamilyFn(
|
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1197,9 +1198,11 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(
|
|||||||
|
|
||||||
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
std::shared_ptr<const NormalizedType> normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||||
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
std::shared_ptr<const NormalizedType> normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||||
|
NormalizationResult lhsInhabited = ctx->normalizer->isInhabited(normLhsTy.get());
|
||||||
|
NormalizationResult rhsInhabited = ctx->normalizer->isInhabited(normRhsTy.get());
|
||||||
|
|
||||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||||
if (!normLhsTy || !normRhsTy)
|
if (!normLhsTy || !normRhsTy || lhsInhabited == NormalizationResult::HitLimits || rhsInhabited == NormalizationResult::HitLimits)
|
||||||
return {std::nullopt, false, {}, {}};
|
return {std::nullopt, false, {}, {}};
|
||||||
|
|
||||||
// if one of the types is error suppressing, we can just go ahead and reduce.
|
// if one of the types is error suppressing, we can just go ahead and reduce.
|
||||||
@ -1207,7 +1210,7 @@ TypeFamilyReductionResult<TypeId> eqFamilyFn(
|
|||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
|
|
||||||
// if we have a `never`, we can never observe that the comparison didn't work.
|
// if we have a `never`, we can never observe that the comparison didn't work.
|
||||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
if (lhsInhabited == NormalizationResult::False || rhsInhabited == NormalizationResult::False)
|
||||||
return {ctx->builtins->booleanType, false, {}, {}};
|
return {ctx->builtins->booleanType, false, {}, {}};
|
||||||
|
|
||||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||||
@ -1282,8 +1285,8 @@ struct FindRefinementBlockers : TypeOnceVisitor
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> refineFamilyFn(
|
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 2 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1340,73 +1343,145 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(
|
|||||||
return {resultTy, false, {}, {}};
|
return {resultTy, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> unionFamilyFn(
|
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
|
{
|
||||||
|
ctx->ice->ice("singleton type family: encountered a type family instance without the required argument structure");
|
||||||
|
LUAU_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeId type = follow(typeParams.at(0));
|
||||||
|
|
||||||
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||||
|
if (isPending(type, ctx->solver))
|
||||||
|
return {std::nullopt, false, {type}, {}};
|
||||||
|
|
||||||
|
TypeId followed = type;
|
||||||
|
// we want to follow through a negation here as well.
|
||||||
|
if (auto negation = get<NegationType>(followed))
|
||||||
|
followed = follow(negation->ty);
|
||||||
|
|
||||||
|
// if we have a singleton type or `nil`, which is its own singleton type...
|
||||||
|
if (get<SingletonType>(followed) || isNil(followed))
|
||||||
|
return {type, false, {}, {}};
|
||||||
|
|
||||||
|
// otherwise, we'll return the top type, `unknown`.
|
||||||
|
return {ctx->builtins->unknownType, false, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
|
{
|
||||||
|
if (!packParams.empty())
|
||||||
{
|
{
|
||||||
ctx->ice->ice("union type family: encountered a type family instance without the required argument structure");
|
ctx->ice->ice("union type family: encountered a type family instance without the required argument structure");
|
||||||
LUAU_ASSERT(false);
|
LUAU_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId lhsTy = follow(typeParams.at(0));
|
// if we only have one parameter, there's nothing to do.
|
||||||
TypeId rhsTy = follow(typeParams.at(1));
|
if (typeParams.size() == 1)
|
||||||
|
return {follow(typeParams[0]), false, {}, {}};
|
||||||
|
|
||||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
// we need to follow all of the type parameters.
|
||||||
if (isPending(lhsTy, ctx->solver))
|
std::vector<TypeId> types;
|
||||||
return {std::nullopt, false, {lhsTy}, {}};
|
types.reserve(typeParams.size());
|
||||||
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
|
for (auto ty : typeParams)
|
||||||
return {rhsTy, false, {}, {}};
|
types.emplace_back(follow(ty));
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
|
||||||
return {std::nullopt, false, {rhsTy}, {}};
|
|
||||||
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
|
|
||||||
return {lhsTy, false, {}, {}};
|
|
||||||
|
|
||||||
|
// unfortunately, we need this short-circuit: if all but one type is `never`, we will return that one type.
|
||||||
|
// this also will early return if _everything_ is `never`, since we already have to check that.
|
||||||
|
std::optional<TypeId> lastType = std::nullopt;
|
||||||
|
for (auto ty : types)
|
||||||
|
{
|
||||||
|
// if we have a previous type and it's not `never` and the current type isn't `never`...
|
||||||
|
if (lastType && !get<NeverType>(lastType) && !get<NeverType>(ty))
|
||||||
|
{
|
||||||
|
// we know we are not taking the short-circuited path.
|
||||||
|
lastType = std::nullopt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, lhsTy, rhsTy);
|
if (get<NeverType>(ty))
|
||||||
if (!result.blockedTypes.empty())
|
continue;
|
||||||
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
lastType = ty;
|
||||||
|
}
|
||||||
|
|
||||||
return {result.result, false, {}, {}};
|
// if we still have a `lastType` at the end, we're taking the short-circuit and reducing early.
|
||||||
|
if (lastType)
|
||||||
|
return {lastType, false, {}, {}};
|
||||||
|
|
||||||
|
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||||
|
for (auto ty : types)
|
||||||
|
if (isPending(ty, ctx->solver))
|
||||||
|
return {std::nullopt, false, {ty}, {}};
|
||||||
|
|
||||||
|
// fold over the types with `simplifyUnion`
|
||||||
|
TypeId resultTy = ctx->builtins->neverType;
|
||||||
|
for (auto ty : types)
|
||||||
|
{
|
||||||
|
SimplifyResult result = simplifyUnion(ctx->builtins, ctx->arena, resultTy, ty);
|
||||||
|
if (!result.blockedTypes.empty())
|
||||||
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||||
|
|
||||||
|
resultTy = result.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {resultTy, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> intersectFamilyFn(
|
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 2 || !packParams.empty())
|
if (!packParams.empty())
|
||||||
{
|
{
|
||||||
ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure");
|
ctx->ice->ice("intersect type family: encountered a type family instance without the required argument structure");
|
||||||
LUAU_ASSERT(false);
|
LUAU_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId lhsTy = follow(typeParams.at(0));
|
// if we only have one parameter, there's nothing to do.
|
||||||
TypeId rhsTy = follow(typeParams.at(1));
|
if (typeParams.size() == 1)
|
||||||
|
return {follow(typeParams[0]), false, {}, {}};
|
||||||
|
|
||||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
// we need to follow all of the type parameters.
|
||||||
if (isPending(lhsTy, ctx->solver))
|
std::vector<TypeId> types;
|
||||||
return {std::nullopt, false, {lhsTy}, {}};
|
types.reserve(typeParams.size());
|
||||||
else if (get<NeverType>(lhsTy)) // if the lhs is never, we don't need this family anymore
|
for (auto ty : typeParams)
|
||||||
return {ctx->builtins->neverType, false, {}, {}};
|
types.emplace_back(follow(ty));
|
||||||
else if (isPending(rhsTy, ctx->solver))
|
|
||||||
return {std::nullopt, false, {rhsTy}, {}};
|
|
||||||
else if (get<NeverType>(rhsTy)) // if the rhs is never, we don't need this family anymore
|
|
||||||
return {ctx->builtins->neverType, false, {}, {}};
|
|
||||||
|
|
||||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, rhsTy);
|
// check to see if the operand types are resolved enough, and wait to reduce if not
|
||||||
if (!result.blockedTypes.empty())
|
// if any of them are `never`, the intersection will always be `never`, so we can reduce directly.
|
||||||
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
for (auto ty : types)
|
||||||
|
{
|
||||||
|
if (isPending(ty, ctx->solver))
|
||||||
|
return {std::nullopt, false, {ty}, {}};
|
||||||
|
else if (get<NeverType>(ty))
|
||||||
|
return {ctx->builtins->neverType, false, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// fold over the types with `simplifyIntersection`
|
||||||
|
TypeId resultTy = ctx->builtins->unknownType;
|
||||||
|
for (auto ty : types)
|
||||||
|
{
|
||||||
|
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
||||||
|
if (!result.blockedTypes.empty())
|
||||||
|
return {std::nullopt, false, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
|
||||||
|
|
||||||
|
resultTy = result.result;
|
||||||
|
}
|
||||||
|
|
||||||
// if the intersection simplifies to `never`, this gives us bad autocomplete.
|
// if the intersection simplifies to `never`, this gives us bad autocomplete.
|
||||||
// we'll just produce the intersection plainly instead, but this might be revisitable
|
// we'll just produce the intersection plainly instead, but this might be revisitable
|
||||||
// if we ever give `never` some kind of "explanation" trail.
|
// if we ever give `never` some kind of "explanation" trail.
|
||||||
if (get<NeverType>(result.result))
|
if (get<NeverType>(resultTy))
|
||||||
{
|
{
|
||||||
TypeId intersection = ctx->arena->addType(IntersectionType{{lhsTy, rhsTy}});
|
TypeId intersection = ctx->arena->addType(IntersectionType{typeParams});
|
||||||
return {intersection, false, {}, {}};
|
return {intersection, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {result.result, false, {}, {}};
|
return {resultTy, false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// computes the keys of `ty` into `result`
|
// computes the keys of `ty` into `result`
|
||||||
@ -1581,8 +1656,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
|
|||||||
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> keyofFamilyFn(
|
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 1 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1593,8 +1668,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyFn(
|
|||||||
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(
|
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||||
TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||||
{
|
{
|
||||||
if (typeParams.size() != 1 || !packParams.empty())
|
if (typeParams.size() != 1 || !packParams.empty())
|
||||||
{
|
{
|
||||||
@ -1623,6 +1698,7 @@ BuiltinTypeFamilies::BuiltinTypeFamilies()
|
|||||||
, leFamily{"le", leFamilyFn}
|
, leFamily{"le", leFamilyFn}
|
||||||
, eqFamily{"eq", eqFamilyFn}
|
, eqFamily{"eq", eqFamilyFn}
|
||||||
, refineFamily{"refine", refineFamilyFn}
|
, refineFamily{"refine", refineFamilyFn}
|
||||||
|
, singletonFamily{"singleton", singletonFamilyFn}
|
||||||
, unionFamily{"union", unionFamilyFn}
|
, unionFamily{"union", unionFamilyFn}
|
||||||
, intersectFamily{"intersect", intersectFamilyFn}
|
, intersectFamily{"intersect", intersectFamilyFn}
|
||||||
, keyofFamily{"keyof", keyofFamilyFn}
|
, keyofFamily{"keyof", keyofFamilyFn}
|
||||||
|
@ -33,6 +33,7 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
|||||||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauSharedSelf, false)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauMetatableInstantiationCloneCheck, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
|
LUAU_FASTFLAGVARIABLE(LuauRemoveBadRelationalOperatorWarning, false)
|
||||||
@ -5632,7 +5633,8 @@ TypeId TypeChecker::instantiateTypeFun(const ScopePtr& scope, const TypeFun& tf,
|
|||||||
TypeId instantiated = *maybeInstantiated;
|
TypeId instantiated = *maybeInstantiated;
|
||||||
|
|
||||||
TypeId target = follow(instantiated);
|
TypeId target = follow(instantiated);
|
||||||
bool needsClone = follow(tf.type) == target;
|
const TableType* tfTable = FFlag::LuauMetatableInstantiationCloneCheck ? getTableType(tf.type) : nullptr;
|
||||||
|
bool needsClone = follow(tf.type) == target || (FFlag::LuauMetatableInstantiationCloneCheck && tfTable != nullptr && tfTable == getTableType(target));
|
||||||
bool shouldMutate = getTableType(tf.type);
|
bool shouldMutate = getTableType(tf.type);
|
||||||
TableType* ttv = getMutableTableType(target);
|
TableType* ttv = getMutableTableType(target);
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauFollowEmptyTypePacks, false);
|
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
@ -271,8 +269,7 @@ TypePackId follow(TypePackId tp, const void* context, TypePackId (*mapper)(const
|
|||||||
|
|
||||||
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped))
|
if (const Unifiable::Bound<TypePackId>* btv = get<Unifiable::Bound<TypePackId>>(mapped))
|
||||||
return btv->boundTo;
|
return btv->boundTo;
|
||||||
else if (const TypePack* tp = get<TypePack>(mapped);
|
else if (const TypePack* tp = get<TypePack>(mapped); tp && tp->head.empty())
|
||||||
(FFlag::DebugLuauDeferredConstraintResolution || FFlag::LuauFollowEmptyTypePacks) && tp && tp->head.empty())
|
|
||||||
return tp->tail;
|
return tp->tail;
|
||||||
else
|
else
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -52,6 +52,11 @@ bool Index::operator==(const Index& other) const
|
|||||||
return index == other.index;
|
return index == other.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Reduction::operator==(const Reduction& other) const
|
||||||
|
{
|
||||||
|
return resultType == other.resultType;
|
||||||
|
}
|
||||||
|
|
||||||
Path Path::append(const Path& suffix) const
|
Path Path::append(const Path& suffix) const
|
||||||
{
|
{
|
||||||
std::vector<Component> joined(components);
|
std::vector<Component> joined(components);
|
||||||
@ -124,6 +129,11 @@ size_t PathHash::operator()(const PackField& field) const
|
|||||||
return static_cast<size_t>(field);
|
return static_cast<size_t>(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t PathHash::operator()(const Reduction& reduction) const
|
||||||
|
{
|
||||||
|
return std::hash<TypeId>()(reduction.resultType);
|
||||||
|
}
|
||||||
|
|
||||||
size_t PathHash::operator()(const Component& component) const
|
size_t PathHash::operator()(const Component& component) const
|
||||||
{
|
{
|
||||||
return visit(*this, component);
|
return visit(*this, component);
|
||||||
@ -472,6 +482,14 @@ struct TraversalState
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool traverse(TypePath::Reduction reduction)
|
||||||
|
{
|
||||||
|
if (checkInvariants())
|
||||||
|
return false;
|
||||||
|
updateCurrent(reduction.resultType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool traverse(TypePath::PackField field)
|
bool traverse(TypePath::PackField field)
|
||||||
{
|
{
|
||||||
if (checkInvariants())
|
if (checkInvariants())
|
||||||
@ -584,9 +602,14 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
|||||||
result << "tail";
|
result << "tail";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result << "()";
|
result << "()";
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, TypePath::Reduction>)
|
||||||
|
{
|
||||||
|
// We need to rework the TypePath system to make subtyping failures easier to understand
|
||||||
|
// https://roblox.atlassian.net/browse/CLI-104422
|
||||||
|
result << "~~>";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static_assert(always_false_v<T>, "Unhandled Component variant");
|
static_assert(always_false_v<T>, "Unhandled Component variant");
|
||||||
|
@ -1006,7 +1006,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||||||
if (!subNorm || !superNorm)
|
if (!subNorm || !superNorm)
|
||||||
reportError(location, NormalizationTooComplex{});
|
reportError(location, NormalizationTooComplex{});
|
||||||
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
|
tryUnifyNormalizedTypes(
|
||||||
|
subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
|
||||||
else
|
else
|
||||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
|
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
|
||||||
}
|
}
|
||||||
@ -1017,7 +1018,8 @@ void Unifier::tryUnifyTypeWithUnion(TypeId subTy, TypeId superTy, const UnionTyp
|
|||||||
if (!subNorm || !superNorm)
|
if (!subNorm || !superNorm)
|
||||||
reportError(location, NormalizationTooComplex{});
|
reportError(location, NormalizationTooComplex{});
|
||||||
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
else if ((failedOptionCount == 1 || foundHeuristic) && failedOption)
|
||||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
|
tryUnifyNormalizedTypes(
|
||||||
|
subTy, superTy, *subNorm, *superNorm, "None of the union options are compatible. For example:", *failedOption);
|
||||||
else
|
else
|
||||||
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
|
tryUnifyNormalizedTypes(subTy, superTy, *subNorm, *superNorm, "none of the union options are compatible");
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,7 @@ static bool areCompatible(TypeId left, TypeId right)
|
|||||||
|
|
||||||
LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
|
LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
|
||||||
|
|
||||||
const TypeId leftType = follow(
|
const TypeId leftType = follow(leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type());
|
||||||
leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
|
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
|
||||||
return true;
|
return true;
|
||||||
@ -52,7 +50,7 @@ static bool areCompatible(TypeId left, TypeId right)
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto& [name, leftProp]: leftTable->props)
|
for (const auto& [name, leftProp] : leftTable->props)
|
||||||
{
|
{
|
||||||
auto it = rightTable->props.find(name);
|
auto it = rightTable->props.find(name);
|
||||||
if (it == rightTable->props.end())
|
if (it == rightTable->props.end())
|
||||||
@ -62,7 +60,7 @@ static bool areCompatible(TypeId left, TypeId right)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [name, rightProp]: rightTable->props)
|
for (const auto& [name, rightProp] : rightTable->props)
|
||||||
{
|
{
|
||||||
auto it = leftTable->props.find(name);
|
auto it = leftTable->props.find(name);
|
||||||
if (it == leftTable->props.end())
|
if (it == leftTable->props.end())
|
||||||
@ -75,6 +73,18 @@ static bool areCompatible(TypeId left, TypeId right)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`.
|
||||||
|
static bool isIrresolvable(TypeId ty)
|
||||||
|
{
|
||||||
|
return get<BlockedType>(ty) || get<TypeFamilyInstanceType>(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns `true` if `tp` is irressolvable and should be added to `incompleteSubtypes`.
|
||||||
|
static bool isIrresolvable(TypePackId tp)
|
||||||
|
{
|
||||||
|
return get<BlockedTypePack>(tp) || get<TypeFamilyInstanceTypePack>(tp);
|
||||||
|
}
|
||||||
|
|
||||||
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
|
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
@ -82,6 +92,19 @@ Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes,
|
|||||||
, ice(ice)
|
, ice(ice)
|
||||||
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
|
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
|
||||||
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
||||||
|
, uninhabitedTypeFamilies(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice,
|
||||||
|
DenseHashSet<const void*>* uninhabitedTypeFamilies)
|
||||||
|
: arena(arena)
|
||||||
|
, builtinTypes(builtinTypes)
|
||||||
|
, scope(scope)
|
||||||
|
, ice(ice)
|
||||||
|
, limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2
|
||||||
|
, recursionLimit(FInt::LuauTypeInferRecursionLimit)
|
||||||
|
, uninhabitedTypeFamilies(uninhabitedTypeFamilies)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +133,11 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||||||
// But we exclude these two subtyping patterns, they are tautological:
|
// But we exclude these two subtyping patterns, they are tautological:
|
||||||
// - never <: *blocked*
|
// - never <: *blocked*
|
||||||
// - *blocked* <: unknown
|
// - *blocked* <: unknown
|
||||||
if ((get<BlockedType>(subTy) || get<BlockedType>(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy))
|
if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy))
|
||||||
{
|
{
|
||||||
|
if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTy) || uninhabitedTypeFamilies->contains(superTy)))
|
||||||
|
return true;
|
||||||
|
|
||||||
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
|
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -473,8 +499,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||||||
if (subTp == superTp)
|
if (subTp == superTp)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (get<BlockedTypePack>(subTp) || get<BlockedTypePack>(superTp))
|
if (isIrresolvable(subTp) || isIrresolvable(superTp))
|
||||||
{
|
{
|
||||||
|
if (uninhabitedTypeFamilies && (uninhabitedTypeFamilies->contains(subTp) || uninhabitedTypeFamilies->contains(superTp)))
|
||||||
|
return true;
|
||||||
|
|
||||||
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
|
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "lobject.h"
|
#include "lobject.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
@ -833,6 +835,27 @@ void analyzeBytecodeTypes(IrFunction& function)
|
|||||||
bcType.result = regTags[ra];
|
bcType.result = regTags[ra];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case LOP_NAMECALL:
|
||||||
|
{
|
||||||
|
if (FFlag::LuauCodegenDirectUserdataFlow)
|
||||||
|
{
|
||||||
|
int ra = LUAU_INSN_A(*pc);
|
||||||
|
int rb = LUAU_INSN_B(*pc);
|
||||||
|
uint32_t kc = pc[1];
|
||||||
|
|
||||||
|
bcType.a = regTags[rb];
|
||||||
|
bcType.b = getBytecodeConstantTag(proto, kc);
|
||||||
|
|
||||||
|
// While namecall might result in a callable table, we assume the function fast path
|
||||||
|
regTags[ra] = LBC_TYPE_FUNCTION;
|
||||||
|
|
||||||
|
// Namecall places source register into target + 1
|
||||||
|
regTags[ra + 1] = bcType.a;
|
||||||
|
|
||||||
|
bcType.result = LBC_TYPE_FUNCTION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case LOP_GETGLOBAL:
|
case LOP_GETGLOBAL:
|
||||||
case LOP_SETGLOBAL:
|
case LOP_SETGLOBAL:
|
||||||
case LOP_CALL:
|
case LOP_CALL:
|
||||||
@ -866,7 +889,6 @@ void analyzeBytecodeTypes(IrFunction& function)
|
|||||||
case LOP_COVERAGE:
|
case LOP_COVERAGE:
|
||||||
case LOP_GETIMPORT:
|
case LOP_GETIMPORT:
|
||||||
case LOP_CAPTURE:
|
case LOP_CAPTURE:
|
||||||
case LOP_NAMECALL:
|
|
||||||
case LOP_PREPVARARGS:
|
case LOP_PREPVARARGS:
|
||||||
case LOP_GETVARARGS:
|
case LOP_GETVARARGS:
|
||||||
case LOP_FORGPREP:
|
case LOP_FORGPREP:
|
||||||
|
@ -163,6 +163,9 @@ bool isUnwindSupported()
|
|||||||
{
|
{
|
||||||
#if defined(_WIN32) && defined(_M_X64)
|
#if defined(_WIN32) && defined(_M_X64)
|
||||||
return true;
|
return true;
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
// Current unwind information is not compatible with Android
|
||||||
|
return false;
|
||||||
#elif defined(__APPLE__) && defined(__aarch64__)
|
#elif defined(__APPLE__) && defined(__aarch64__)
|
||||||
char ver[256];
|
char ver[256];
|
||||||
size_t verLength = sizeof(ver);
|
size_t verLength = sizeof(ver);
|
||||||
|
@ -122,7 +122,10 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
|||||||
if (stats && (stats->functionStatsFlags & FunctionStats_Enable))
|
if (stats && (stats->functionStatsFlags & FunctionStats_Enable))
|
||||||
{
|
{
|
||||||
FunctionStats functionStat;
|
FunctionStats functionStat;
|
||||||
functionStat.name = p->debugname ? getstr(p->debugname) : "";
|
|
||||||
|
// function name is empty for anonymous and pseudo top-level functions
|
||||||
|
// properly name pseudo top-level function because it will be compiled natively if it has loops
|
||||||
|
functionStat.name = p->debugname ? getstr(p->debugname) : p->bytecodeid == root->bytecodeid ? "[top level]" : "[anonymous]";
|
||||||
functionStat.line = p->linedefined;
|
functionStat.line = p->linedefined;
|
||||||
functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode);
|
functionStat.bcodeCount = getInstructionCount(p->code, p->sizecode);
|
||||||
functionStat.irCount = unsigned(ir.function.instructions.size());
|
functionStat.irCount = unsigned(ir.function.instructions.size());
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "ltm.h"
|
#include "ltm.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false)
|
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
@ -1230,6 +1231,14 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
|
||||||
|
{
|
||||||
|
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
|
||||||
|
|
||||||
|
build.inst(IrCmd::FALLBACK_GETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||||
@ -1256,10 +1265,20 @@ void translateInstSetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
|||||||
int rb = LUAU_INSN_B(*pc);
|
int rb = LUAU_INSN_B(*pc);
|
||||||
uint32_t aux = pc[1];
|
uint32_t aux = pc[1];
|
||||||
|
|
||||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
|
||||||
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||||
|
|
||||||
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
IrOp tb = build.inst(IrCmd::LOAD_TAG, build.vmReg(rb));
|
||||||
|
|
||||||
|
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
|
||||||
|
{
|
||||||
|
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TUSERDATA), build.vmExit(pcpos));
|
||||||
|
|
||||||
|
build.inst(IrCmd::FALLBACK_SETTABLEKS, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
|
|
||||||
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
build.inst(IrCmd::CHECK_TAG, tb, build.constTag(LUA_TTABLE), bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||||
|
|
||||||
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||||
@ -1370,12 +1389,31 @@ void translateInstNamecall(IrBuilder& build, const Instruction* pc, int pcpos)
|
|||||||
int rb = LUAU_INSN_B(*pc);
|
int rb = LUAU_INSN_B(*pc);
|
||||||
uint32_t aux = pc[1];
|
uint32_t aux = pc[1];
|
||||||
|
|
||||||
|
BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos);
|
||||||
|
|
||||||
|
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_VECTOR)
|
||||||
|
{
|
||||||
|
build.loadAndCheckTag(build.vmReg(rb), LUA_TVECTOR, build.vmExit(pcpos));
|
||||||
|
|
||||||
|
build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_USERDATA)
|
||||||
|
{
|
||||||
|
build.loadAndCheckTag(build.vmReg(rb), LUA_TUSERDATA, build.vmExit(pcpos));
|
||||||
|
|
||||||
|
build.inst(IrCmd::FALLBACK_NAMECALL, build.constUint(pcpos), build.vmReg(ra), build.vmReg(rb), build.vmConst(aux));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL));
|
IrOp next = build.blockAtInst(pcpos + getOpLength(LOP_NAMECALL));
|
||||||
IrOp fallback = build.block(IrBlockKind::Fallback);
|
IrOp fallback = build.block(IrBlockKind::Fallback);
|
||||||
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
|
IrOp firstFastPathSuccess = build.block(IrBlockKind::Internal);
|
||||||
IrOp secondFastPath = build.block(IrBlockKind::Internal);
|
IrOp secondFastPath = build.block(IrBlockKind::Internal);
|
||||||
|
|
||||||
build.loadAndCheckTag(build.vmReg(rb), LUA_TTABLE, fallback);
|
build.loadAndCheckTag(
|
||||||
|
build.vmReg(rb), LUA_TTABLE, FFlag::LuauCodegenDirectUserdataFlow && bcTypes.a == LBC_TYPE_TABLE ? build.vmExit(pcpos) : fallback);
|
||||||
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
IrOp table = build.inst(IrCmd::LOAD_POINTER, build.vmReg(rb));
|
||||||
|
|
||||||
CODEGEN_ASSERT(build.function.proto);
|
CODEGEN_ASSERT(build.function.proto);
|
||||||
|
@ -11,9 +11,9 @@ inline bool isFlagExperimental(const char* flag)
|
|||||||
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
// Flags in this list are disabled by default in various command-line tools. They may have behavior that is not fully final,
|
||||||
// or critical bugs that are found after the code has been submitted.
|
// or critical bugs that are found after the code has been submitted.
|
||||||
static const char* const kList[] = {
|
static const char* const kList[] = {
|
||||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||||
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
|
||||||
// makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
nullptr,
|
nullptr,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2023 Roblox Corporation
|
Copyright (c) 2019-2024 Roblox Corporation
|
||||||
Copyright (c) 1994–2019 Lua.org, PUC-Rio.
|
Copyright (c) 1994–2019 Lua.org, PUC-Rio.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
@ -929,7 +929,7 @@ static size_t gcstep(lua_State* L, size_t limit)
|
|||||||
{
|
{
|
||||||
while (g->sweepgcopage && cost < limit)
|
while (g->sweepgcopage && cost < limit)
|
||||||
{
|
{
|
||||||
lua_Page* next = luaM_getnextgcopage(g->sweepgcopage); // page sweep might destroy the page
|
lua_Page* next = luaM_getnextpage(g->sweepgcopage); // page sweep might destroy the page
|
||||||
|
|
||||||
int steps = sweepgcopage(L, g->sweepgcopage);
|
int steps = sweepgcopage(L, g->sweepgcopage);
|
||||||
|
|
||||||
|
@ -196,9 +196,9 @@ struct lua_Page
|
|||||||
lua_Page* prev;
|
lua_Page* prev;
|
||||||
lua_Page* next;
|
lua_Page* next;
|
||||||
|
|
||||||
// list of all gco pages
|
// list of all pages
|
||||||
lua_Page* gcolistprev;
|
lua_Page* listprev;
|
||||||
lua_Page* gcolistnext;
|
lua_Page* listnext;
|
||||||
|
|
||||||
int pageSize; // page size in bytes, including page header
|
int pageSize; // page size in bytes, including page header
|
||||||
int blockSize; // block size in bytes, including block header (for non-GCO)
|
int blockSize; // block size in bytes, including block header (for non-GCO)
|
||||||
@ -220,7 +220,7 @@ l_noret luaM_toobig(lua_State* L)
|
|||||||
luaG_runerror(L, "memory allocation error: block too big");
|
luaG_runerror(L, "memory allocation error: block too big");
|
||||||
}
|
}
|
||||||
|
|
||||||
static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int blockSize, int blockCount)
|
static lua_Page* newpage(lua_State* L, lua_Page** pageset, int pageSize, int blockSize, int blockCount)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
@ -236,8 +236,8 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
|||||||
page->prev = NULL;
|
page->prev = NULL;
|
||||||
page->next = NULL;
|
page->next = NULL;
|
||||||
|
|
||||||
page->gcolistprev = NULL;
|
page->listprev = NULL;
|
||||||
page->gcolistnext = NULL;
|
page->listnext = NULL;
|
||||||
|
|
||||||
page->pageSize = pageSize;
|
page->pageSize = pageSize;
|
||||||
page->blockSize = blockSize;
|
page->blockSize = blockSize;
|
||||||
@ -249,12 +249,12 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
|||||||
page->freeNext = (blockCount - 1) * blockSize;
|
page->freeNext = (blockCount - 1) * blockSize;
|
||||||
page->busyBlocks = 0;
|
page->busyBlocks = 0;
|
||||||
|
|
||||||
if (gcopageset)
|
if (pageset)
|
||||||
{
|
{
|
||||||
page->gcolistnext = *gcopageset;
|
page->listnext = *pageset;
|
||||||
if (page->gcolistnext)
|
if (page->listnext)
|
||||||
page->gcolistnext->gcolistprev = page;
|
page->listnext->listprev = page;
|
||||||
*gcopageset = page;
|
*pageset = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
@ -263,7 +263,7 @@ static lua_Page* newpage(lua_State* L, lua_Page** gcopageset, int pageSize, int
|
|||||||
// this is part of a cold path in newblock and newgcoblock
|
// this is part of a cold path in newblock and newgcoblock
|
||||||
// it is marked as noinline to prevent it from being inlined into those functions
|
// it is marked as noinline to prevent it from being inlined into those functions
|
||||||
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
|
// if it is inlined, then the compiler may determine those functions are "too big" to be profitably inlined, which results in reduced performance
|
||||||
LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, uint8_t sizeClass, bool storeMetadata)
|
LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, uint8_t sizeClass, bool storeMetadata)
|
||||||
{
|
{
|
||||||
if (FFlag::LuauExtendedSizeClasses)
|
if (FFlag::LuauExtendedSizeClasses)
|
||||||
{
|
{
|
||||||
@ -272,7 +272,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
|
|||||||
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
|
int blockSize = sizeOfClass + (storeMetadata ? kBlockHeader : 0);
|
||||||
int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize;
|
int blockCount = (pageSize - offsetof(lua_Page, data)) / blockSize;
|
||||||
|
|
||||||
lua_Page* page = newpage(L, gcopageset, pageSize, blockSize, blockCount);
|
lua_Page* page = newpage(L, pageset, pageSize, blockSize, blockCount);
|
||||||
|
|
||||||
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
||||||
LUAU_ASSERT(!freepageset[sizeClass]);
|
LUAU_ASSERT(!freepageset[sizeClass]);
|
||||||
@ -285,7 +285,7 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
|
|||||||
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
|
int blockSize = kSizeClassConfig.sizeOfClass[sizeClass] + (storeMetadata ? kBlockHeader : 0);
|
||||||
int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize;
|
int blockCount = (kSmallPageSize - offsetof(lua_Page, data)) / blockSize;
|
||||||
|
|
||||||
lua_Page* page = newpage(L, gcopageset, kSmallPageSize, blockSize, blockCount);
|
lua_Page* page = newpage(L, pageset, kSmallPageSize, blockSize, blockCount);
|
||||||
|
|
||||||
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
// prepend a page to page freelist (which is empty because we only ever allocate a new page when it is!)
|
||||||
LUAU_ASSERT(!freepageset[sizeClass]);
|
LUAU_ASSERT(!freepageset[sizeClass]);
|
||||||
@ -295,27 +295,27 @@ LUAU_NOINLINE static lua_Page* newclasspage(lua_State* L, lua_Page** freepageset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freepage(lua_State* L, lua_Page** gcopageset, lua_Page* page)
|
static void freepage(lua_State* L, lua_Page** pageset, lua_Page* page)
|
||||||
{
|
{
|
||||||
global_State* g = L->global;
|
global_State* g = L->global;
|
||||||
|
|
||||||
if (gcopageset)
|
if (pageset)
|
||||||
{
|
{
|
||||||
// remove page from alllist
|
// remove page from alllist
|
||||||
if (page->gcolistnext)
|
if (page->listnext)
|
||||||
page->gcolistnext->gcolistprev = page->gcolistprev;
|
page->listnext->listprev = page->listprev;
|
||||||
|
|
||||||
if (page->gcolistprev)
|
if (page->listprev)
|
||||||
page->gcolistprev->gcolistnext = page->gcolistnext;
|
page->listprev->listnext = page->listnext;
|
||||||
else if (*gcopageset == page)
|
else if (*pageset == page)
|
||||||
*gcopageset = page->gcolistnext;
|
*pageset = page->listnext;
|
||||||
}
|
}
|
||||||
|
|
||||||
// so long
|
// so long
|
||||||
(*g->frealloc)(g->ud, page, page->pageSize, 0);
|
(*g->frealloc)(g->ud, page, page->pageSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopageset, lua_Page* page, uint8_t sizeClass)
|
static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** pageset, lua_Page* page, uint8_t sizeClass)
|
||||||
{
|
{
|
||||||
// remove page from freelist
|
// remove page from freelist
|
||||||
if (page->next)
|
if (page->next)
|
||||||
@ -326,7 +326,7 @@ static void freeclasspage(lua_State* L, lua_Page** freepageset, lua_Page** gcopa
|
|||||||
else if (freepageset[sizeClass] == page)
|
else if (freepageset[sizeClass] == page)
|
||||||
freepageset[sizeClass] = page->next;
|
freepageset[sizeClass] = page->next;
|
||||||
|
|
||||||
freepage(L, gcopageset, page);
|
freepage(L, pageset, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* newblock(lua_State* L, int sizeClass)
|
static void* newblock(lua_State* L, int sizeClass)
|
||||||
@ -645,9 +645,9 @@ void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blo
|
|||||||
*pageSize = page->pageSize;
|
*pageSize = page->pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_Page* luaM_getnextgcopage(lua_Page* page)
|
lua_Page* luaM_getnextpage(lua_Page* page)
|
||||||
{
|
{
|
||||||
return page->gcolistnext;
|
return page->listnext;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco))
|
||||||
@ -684,7 +684,7 @@ void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, l
|
|||||||
|
|
||||||
for (lua_Page* curr = g->allgcopages; curr;)
|
for (lua_Page* curr = g->allgcopages; curr;)
|
||||||
{
|
{
|
||||||
lua_Page* next = curr->gcolistnext; // block visit might destroy the page
|
lua_Page* next = curr->listnext; // block visit might destroy the page
|
||||||
|
|
||||||
luaM_visitpage(curr, context, visitor);
|
luaM_visitpage(curr, context, visitor);
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ LUAI_FUNC l_noret luaM_toobig(lua_State* L);
|
|||||||
|
|
||||||
LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize);
|
LUAI_FUNC void luaM_getpagewalkinfo(lua_Page* page, char** start, char** end, int* busyBlocks, int* blockSize);
|
||||||
LUAI_FUNC void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blockSize, int* pageSize);
|
LUAI_FUNC void luaM_getpageinfo(lua_Page* page, int* pageBlocks, int* busyBlocks, int* blockSize, int* pageSize);
|
||||||
LUAI_FUNC lua_Page* luaM_getnextgcopage(lua_Page* page);
|
LUAI_FUNC lua_Page* luaM_getnextpage(lua_Page* page);
|
||||||
|
|
||||||
LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
LUAI_FUNC void luaM_visitpage(lua_Page* page, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
||||||
LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
LUAI_FUNC void luaM_visitgco(lua_State* L, void* context, bool (*visitor)(void* context, lua_Page* page, GCObject* gco));
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "lualib.h"
|
#include "lualib.h"
|
||||||
|
|
||||||
#include "lapi.h"
|
#include "lapi.h"
|
||||||
|
#include "lnumutils.h"
|
||||||
#include "lstate.h"
|
#include "lstate.h"
|
||||||
#include "ltable.h"
|
#include "ltable.h"
|
||||||
#include "lstring.h"
|
#include "lstring.h"
|
||||||
@ -10,6 +11,8 @@
|
|||||||
#include "ldebug.h"
|
#include "ldebug.h"
|
||||||
#include "lvm.h"
|
#include "lvm.h"
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false)
|
||||||
|
|
||||||
static int foreachi(lua_State* L)
|
static int foreachi(lua_State* L)
|
||||||
{
|
{
|
||||||
luaL_checktype(L, 1, LUA_TTABLE);
|
luaL_checktype(L, 1, LUA_TTABLE);
|
||||||
@ -112,6 +115,68 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t)
|
|||||||
|
|
||||||
luaC_barrierfast(L, dst);
|
luaC_barrierfast(L, dst);
|
||||||
}
|
}
|
||||||
|
else if (DFFlag::LuauFastCrossTableMove && dst != src)
|
||||||
|
{
|
||||||
|
// compute the array slice we have to copy over
|
||||||
|
int slicestart = f < 1 ? 0 : (f > src->sizearray ? src->sizearray : f - 1);
|
||||||
|
int sliceend = e < 1 ? 0 : (e > src->sizearray ? src->sizearray : e);
|
||||||
|
LUAU_ASSERT(slicestart <= sliceend);
|
||||||
|
|
||||||
|
int slicecount = sliceend - slicestart;
|
||||||
|
|
||||||
|
if (slicecount > 0)
|
||||||
|
{
|
||||||
|
// array slice starting from INT_MIN is impossible, so we don't have to worry about int overflow
|
||||||
|
int dstslicestart = f < 1 ? -f + 1 : 0;
|
||||||
|
|
||||||
|
// copy over the slice
|
||||||
|
for (int i = 0; i < slicecount; ++i)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, srct, slicestart + i + 1);
|
||||||
|
lua_rawseti(L, dstt, dstslicestart + t + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the remaining elements that could be in the hash part
|
||||||
|
int hashpartsize = sizenode(src);
|
||||||
|
|
||||||
|
// select the strategy with the least amount of steps
|
||||||
|
if (n <= hashpartsize)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
// skip array slice elements that were already copied over
|
||||||
|
if (cast_to(unsigned int, f + i - 1) < cast_to(unsigned int, src->sizearray))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lua_rawgeti(L, srct, f + i);
|
||||||
|
lua_rawseti(L, dstt, t + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// source and destination tables are different, so we can iterate over source hash part directly
|
||||||
|
int i = hashpartsize;
|
||||||
|
|
||||||
|
while (i--)
|
||||||
|
{
|
||||||
|
LuaNode* node = gnode(src, i);
|
||||||
|
if (ttisnumber(gkey(node)))
|
||||||
|
{
|
||||||
|
double n = nvalue(gkey(node));
|
||||||
|
|
||||||
|
int k;
|
||||||
|
luai_num2int(k, n);
|
||||||
|
|
||||||
|
if (luai_numeq(cast_num(k), n) && k >= f && k <= e)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, srct, k);
|
||||||
|
lua_rawseti(L, dstt, t - f + k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (t > e || t <= f || dst != src)
|
if (t > e || t <= f || dst != src)
|
||||||
|
@ -145,36 +145,38 @@ struct ACBuiltinsFixture : ACFixtureImpl<BuiltinsFixture>
|
|||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
#define LUAU_CHECK_HAS_KEY(map, key) do \
|
#define LUAU_CHECK_HAS_KEY(map, key) \
|
||||||
{ \
|
do \
|
||||||
auto&& _m = (map); \
|
{ \
|
||||||
auto&& _k = (key); \
|
auto&& _m = (map); \
|
||||||
const size_t count = _m.count(_k); \
|
auto&& _k = (key); \
|
||||||
|
const size_t count = _m.count(_k); \
|
||||||
CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
|
CHECK_MESSAGE(count, "Map should have key \"" << _k << "\""); \
|
||||||
if (!count) \
|
if (!count) \
|
||||||
{ \
|
{ \
|
||||||
MESSAGE("Keys: (count " << _m.size() << ")"); \
|
MESSAGE("Keys: (count " << _m.size() << ")"); \
|
||||||
for (const auto& [k, v]: _m) \
|
for (const auto& [k, v] : _m) \
|
||||||
{ \
|
{ \
|
||||||
MESSAGE("\tkey: " << k); \
|
MESSAGE("\tkey: " << k); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
#define LUAU_CHECK_HAS_NO_KEY(map, key) do \
|
#define LUAU_CHECK_HAS_NO_KEY(map, key) \
|
||||||
{ \
|
do \
|
||||||
auto&& _m = (map); \
|
{ \
|
||||||
auto&& _k = (key); \
|
auto&& _m = (map); \
|
||||||
const size_t count = _m.count(_k); \
|
auto&& _k = (key); \
|
||||||
|
const size_t count = _m.count(_k); \
|
||||||
CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
|
CHECK_MESSAGE(!count, "Map should not have key \"" << _k << "\""); \
|
||||||
if (count) \
|
if (count) \
|
||||||
{ \
|
{ \
|
||||||
MESSAGE("Keys: (count " << _m.size() << ")"); \
|
MESSAGE("Keys: (count " << _m.size() << ")"); \
|
||||||
for (const auto& [k, v]: _m) \
|
for (const auto& [k, v] : _m) \
|
||||||
{ \
|
{ \
|
||||||
MESSAGE("\tkey: " << k); \
|
MESSAGE("\tkey: " << k); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("AutocompleteTest");
|
TEST_SUITE_BEGIN("AutocompleteTest");
|
||||||
|
@ -29,8 +29,7 @@ ClassFixture::ClassFixture()
|
|||||||
};
|
};
|
||||||
|
|
||||||
getMutable<ClassType>(connectionType)->props = {
|
getMutable<ClassType>(connectionType)->props = {
|
||||||
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}
|
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}};
|
||||||
};
|
|
||||||
|
|
||||||
TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassType>(baseClassType)->props = {
|
getMutable<ClassType>(baseClassType)->props = {
|
||||||
@ -103,13 +102,10 @@ ClassFixture::ClassFixture()
|
|||||||
};
|
};
|
||||||
getMutable<TableType>(vector2MetaType)->props = {
|
getMutable<TableType>(vector2MetaType)->props = {
|
||||||
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
|
{"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}},
|
||||||
{"__mul", {
|
{"__mul", {arena.addType(IntersectionType{{
|
||||||
arena.addType(IntersectionType{{
|
makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}),
|
||||||
makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}),
|
makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}),
|
||||||
makeFunction(arena, vector2InstanceType, {builtinTypes->numberType}, {vector2InstanceType}),
|
}})}}};
|
||||||
}})
|
|
||||||
}}
|
|
||||||
};
|
|
||||||
globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
|
||||||
addGlobalBinding(globals, "Vector2", vector2Type, "@test");
|
addGlobalBinding(globals, "Vector2", vector2Type, "@test");
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
|
|||||||
LUAU_FASTFLAG(LuauCodegenInferNumTag)
|
LUAU_FASTFLAG(LuauCodegenInferNumTag)
|
||||||
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
|
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
|
||||||
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
|
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauFastCrossTableMove)
|
||||||
|
|
||||||
static lua_CompileOptions defaultOptions()
|
static lua_CompileOptions defaultOptions()
|
||||||
{
|
{
|
||||||
@ -415,6 +416,8 @@ TEST_CASE("Sort")
|
|||||||
|
|
||||||
TEST_CASE("Move")
|
TEST_CASE("Move")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag luauFastCrossTableMove{DFFlag::LuauFastCrossTableMove, true};
|
||||||
|
|
||||||
runConformance("move.lua");
|
runConformance("move.lua");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1837,7 +1840,7 @@ TEST_CASE("DebugApi")
|
|||||||
lua_pushnumber(L, 10);
|
lua_pushnumber(L, 10);
|
||||||
|
|
||||||
lua_Debug ar;
|
lua_Debug ar;
|
||||||
CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function
|
CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function
|
||||||
CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack
|
CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2174,8 +2177,7 @@ TEST_CASE("HugeFunctionLoadFailure")
|
|||||||
static size_t largeAllocationToFail = 0;
|
static size_t largeAllocationToFail = 0;
|
||||||
static size_t largeAllocationCount = 0;
|
static size_t largeAllocationCount = 0;
|
||||||
|
|
||||||
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void*
|
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void* {
|
||||||
{
|
|
||||||
if (nsize == 0)
|
if (nsize == 0)
|
||||||
{
|
{
|
||||||
free(ptr);
|
free(ptr);
|
||||||
|
@ -19,12 +19,10 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation")
|
|||||||
|
|
||||||
TypeId genericT = arena.addType(GenericType{"T"});
|
TypeId genericT = arena.addType(GenericType{"T"});
|
||||||
|
|
||||||
TypeId idTy = arena.addType(FunctionType{
|
TypeId idTy = arena.addType(FunctionType{/* generics */ {genericT},
|
||||||
/* generics */ {genericT},
|
|
||||||
/* genericPacks */ {},
|
/* genericPacks */ {},
|
||||||
/* argTypes */ arena.addTypePack({genericT}),
|
/* argTypes */ arena.addTypePack({genericT}),
|
||||||
/* retTypes */ arena.addTypePack({genericT})
|
/* retTypes */ arena.addTypePack({genericT})});
|
||||||
});
|
|
||||||
|
|
||||||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||||
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
|
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
|
||||||
|
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
|
||||||
|
|
||||||
static std::string getCodegenAssembly(const char* source)
|
static std::string getCodegenAssembly(const char* source)
|
||||||
{
|
{
|
||||||
@ -402,7 +403,7 @@ local function vecrcp(a: vector)
|
|||||||
return vector(1, 2, 3) + a
|
return vector(1, 2, 3) + a
|
||||||
end
|
end
|
||||||
)"),
|
)"),
|
||||||
R"(
|
R"(
|
||||||
; function vecrcp($arg0) line 2
|
; function vecrcp($arg0) line 2
|
||||||
bb_0:
|
bb_0:
|
||||||
CHECK_TAG R0, tvector, exit(entry)
|
CHECK_TAG R0, tvector, exit(entry)
|
||||||
@ -420,4 +421,128 @@ bb_bytecode_1:
|
|||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("VectorNamecall")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||||
|
|
||||||
|
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||||
|
local function abs(a: vector)
|
||||||
|
return a:Abs()
|
||||||
|
end
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
; function abs($arg0) line 2
|
||||||
|
bb_0:
|
||||||
|
CHECK_TAG R0, tvector, exit(entry)
|
||||||
|
JUMP bb_2
|
||||||
|
bb_2:
|
||||||
|
JUMP bb_bytecode_1
|
||||||
|
bb_bytecode_1:
|
||||||
|
FALLBACK_NAMECALL 0u, R1, R0, K0
|
||||||
|
INTERRUPT 2u
|
||||||
|
SET_SAVEDPC 3u
|
||||||
|
CALL R1, 1i, -1i
|
||||||
|
INTERRUPT 3u
|
||||||
|
RETURN R1, -1i
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UserDataGetIndex")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||||
|
|
||||||
|
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||||
|
local function getxy(a: Point)
|
||||||
|
return a.x + a.y
|
||||||
|
end
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
; function getxy($arg0) line 2
|
||||||
|
bb_0:
|
||||||
|
CHECK_TAG R0, tuserdata, exit(entry)
|
||||||
|
JUMP bb_2
|
||||||
|
bb_2:
|
||||||
|
JUMP bb_bytecode_1
|
||||||
|
bb_bytecode_1:
|
||||||
|
FALLBACK_GETTABLEKS 0u, R2, R0, K0
|
||||||
|
FALLBACK_GETTABLEKS 2u, R3, R0, K1
|
||||||
|
CHECK_TAG R2, tnumber, bb_fallback_3
|
||||||
|
CHECK_TAG R3, tnumber, bb_fallback_3
|
||||||
|
%14 = LOAD_DOUBLE R2
|
||||||
|
%16 = ADD_NUM %14, R3
|
||||||
|
STORE_DOUBLE R1, %16
|
||||||
|
STORE_TAG R1, tnumber
|
||||||
|
JUMP bb_4
|
||||||
|
bb_4:
|
||||||
|
INTERRUPT 5u
|
||||||
|
RETURN R1, 1i
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UserDataSetIndex")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||||
|
|
||||||
|
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||||
|
local function setxy(a: Point)
|
||||||
|
a.x = 3
|
||||||
|
a.y = 4
|
||||||
|
end
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
; function setxy($arg0) line 2
|
||||||
|
bb_0:
|
||||||
|
CHECK_TAG R0, tuserdata, exit(entry)
|
||||||
|
JUMP bb_2
|
||||||
|
bb_2:
|
||||||
|
JUMP bb_bytecode_1
|
||||||
|
bb_bytecode_1:
|
||||||
|
STORE_DOUBLE R1, 3
|
||||||
|
STORE_TAG R1, tnumber
|
||||||
|
FALLBACK_SETTABLEKS 1u, R1, R0, K0
|
||||||
|
STORE_DOUBLE R1, 4
|
||||||
|
FALLBACK_SETTABLEKS 4u, R1, R0, K1
|
||||||
|
INTERRUPT 6u
|
||||||
|
RETURN R0, 0i
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UserDataNamecall")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||||
|
|
||||||
|
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||||
|
local function getxy(a: Point)
|
||||||
|
return a:GetX() + a:GetY()
|
||||||
|
end
|
||||||
|
)"),
|
||||||
|
R"(
|
||||||
|
; function getxy($arg0) line 2
|
||||||
|
bb_0:
|
||||||
|
CHECK_TAG R0, tuserdata, exit(entry)
|
||||||
|
JUMP bb_2
|
||||||
|
bb_2:
|
||||||
|
JUMP bb_bytecode_1
|
||||||
|
bb_bytecode_1:
|
||||||
|
FALLBACK_NAMECALL 0u, R2, R0, K0
|
||||||
|
INTERRUPT 2u
|
||||||
|
SET_SAVEDPC 3u
|
||||||
|
CALL R2, 1i, 1i
|
||||||
|
FALLBACK_NAMECALL 3u, R3, R0, K1
|
||||||
|
INTERRUPT 5u
|
||||||
|
SET_SAVEDPC 6u
|
||||||
|
CALL R3, 1i, 1i
|
||||||
|
CHECK_TAG R2, tnumber, bb_fallback_3
|
||||||
|
CHECK_TAG R3, tnumber, bb_fallback_3
|
||||||
|
%20 = LOAD_DOUBLE R2
|
||||||
|
%22 = ADD_NUM %20, R3
|
||||||
|
STORE_DOUBLE R1, %22
|
||||||
|
STORE_TAG R1, tnumber
|
||||||
|
JUMP bb_4
|
||||||
|
bb_4:
|
||||||
|
INTERRUPT 7u
|
||||||
|
RETURN R1, 1i
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
#include "Luau/BuiltinDefinitions.h"
|
#include "Luau/BuiltinDefinitions.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTFLAG(LuauFixNormalizeCaching);
|
LUAU_FASTFLAG(LuauFixNormalizeCaching)
|
||||||
|
LUAU_FASTFLAG(LuauNormalizeNotUnknownIntersection)
|
||||||
|
LUAU_FASTFLAG(LuauFixCyclicUnionsOfIntersections);
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
@ -797,6 +799,36 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union")
|
|||||||
CHECK("number" == toString(normalizer.typeFromNormal(*nt)));
|
CHECK("number" == toString(normalizer.typeFromNormal(*nt)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true};
|
||||||
|
|
||||||
|
// t1 where t1 = (string & t1) | string
|
||||||
|
TypeId boundTy = arena.addType(BlockedType{});
|
||||||
|
TypeId intersectTy = arena.addType(IntersectionType{{builtinTypes->stringType, boundTy}});
|
||||||
|
TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, intersectTy}});
|
||||||
|
asMutable(boundTy)->reassign(Type{BoundType{unionTy}});
|
||||||
|
|
||||||
|
std::shared_ptr<const NormalizedType> nt = normalizer.normalize(unionTy);
|
||||||
|
|
||||||
|
CHECK("string" == toString(normalizer.typeFromNormal(*nt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauFixCyclicUnionsOfIntersections, true};
|
||||||
|
|
||||||
|
// t1 where t1 = (string & t1) | string
|
||||||
|
TypeId boundTy = arena.addType(BlockedType{});
|
||||||
|
TypeId unionTy = arena.addType(UnionType{{builtinTypes->stringType, boundTy}});
|
||||||
|
TypeId intersectionTy = arena.addType(IntersectionType{{builtinTypes->stringType, unionTy}});
|
||||||
|
asMutable(boundTy)->reassign(Type{BoundType{intersectionTy}});
|
||||||
|
|
||||||
|
std::shared_ptr<const NormalizedType> nt = normalizer.normalize(intersectionTy);
|
||||||
|
|
||||||
|
CHECK("string" == toString(normalizer.typeFromNormal(*nt)));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
|
TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
|
||||||
{
|
{
|
||||||
CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>")));
|
CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>")));
|
||||||
@ -919,4 +951,15 @@ TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_n
|
|||||||
CHECK(na1 != na2);
|
CHECK(na1 != na2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::LuauNormalizeNotUnknownIntersection, true};
|
||||||
|
|
||||||
|
TypeId notUnknown = arena.addType(NegationType{builtinTypes->unknownType});
|
||||||
|
TypeId type = arena.addType(IntersectionType{{builtinTypes->numberType, notUnknown}});
|
||||||
|
std::shared_ptr<const NormalizedType> normalized = normalizer.normalize(type);
|
||||||
|
|
||||||
|
CHECK("never" == toString(normalizer.typeFromNormal(*normalized.get())));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -225,9 +225,9 @@ struct SubtypeFixture : Fixture
|
|||||||
});
|
});
|
||||||
|
|
||||||
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
|
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
|
||||||
{"X", Property::readonly(builtinTypes->numberType)},
|
{"X", Property::readonly(builtinTypes->numberType)},
|
||||||
{"Y", Property::readonly(builtinTypes->numberType)},
|
{"Y", Property::readonly(builtinTypes->numberType)},
|
||||||
});
|
});
|
||||||
|
|
||||||
// "hello" | "hello"
|
// "hello" | "hello"
|
||||||
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
||||||
@ -1285,6 +1285,30 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }
|
|||||||
CHECK_IS_SUBTYPE(tableToPropType, otherType);
|
CHECK_IS_SUBTYPE(tableToPropType, otherType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_family_instance")
|
||||||
|
{
|
||||||
|
TypeId longTy = arena.addType(UnionType{{builtinTypes->booleanType, builtinTypes->bufferType, builtinTypes->classType, builtinTypes->functionType,
|
||||||
|
builtinTypes->numberType, builtinTypes->stringType, builtinTypes->tableType, builtinTypes->threadType}});
|
||||||
|
TypeId tblTy = tbl({{"depth", builtinTypes->unknownType}});
|
||||||
|
TypeId combined = meet(longTy, tblTy);
|
||||||
|
TypeId subTy = arena.addType(TypeFamilyInstanceType{NotNull{&builtinTypeFamilies.unionFamily}, {combined, builtinTypes->neverType}, {}});
|
||||||
|
TypeId superTy = builtinTypes->neverType;
|
||||||
|
SubtypingResult result = isSubtype(subTy, superTy);
|
||||||
|
CHECK(!result.isSubtype);
|
||||||
|
|
||||||
|
for (const SubtypingReasoning& reasoning : result.reasoning)
|
||||||
|
{
|
||||||
|
if (reasoning.subPath.empty() && reasoning.superPath.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::optional<TypeOrPack> optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes);
|
||||||
|
std::optional<TypeOrPack> optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes);
|
||||||
|
|
||||||
|
if (!optSubLeaf || !optSuperLeaf)
|
||||||
|
CHECK(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("Subtyping.Subpaths");
|
TEST_SUITE_BEGIN("Subtyping.Subpaths");
|
||||||
|
@ -944,7 +944,8 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
|||||||
|
|
||||||
std::string expected;
|
std::string expected;
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
expected = R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
|
expected =
|
||||||
|
R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
|
||||||
else
|
else
|
||||||
expected = R"(Type
|
expected = R"(Type
|
||||||
'{ a: number, b: string, c: { d: string } }'
|
'{ a: number, b: string, c: { d: string } }'
|
||||||
@ -1013,15 +1014,8 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack")
|
|||||||
TypePack* packPtr = getMutable<TypePack>(thePack);
|
TypePack* packPtr = getMutable<TypePack>(thePack);
|
||||||
REQUIRE(packPtr);
|
REQUIRE(packPtr);
|
||||||
|
|
||||||
const TableType::Props theProps = {
|
const TableType::Props theProps = {{"BaseField", Property::readonly(builtinTypes->unknownType)},
|
||||||
{"BaseField", Property::readonly(builtinTypes->unknownType)},
|
{"BaseMethod", Property::readonly(arena.addType(FunctionType{thePack, arena.addTypePack({})}))}};
|
||||||
{"BaseMethod", Property::readonly(arena.addType(
|
|
||||||
FunctionType{
|
|
||||||
thePack,
|
|
||||||
arena.addTypePack({})
|
|
||||||
}
|
|
||||||
))}
|
|
||||||
};
|
|
||||||
|
|
||||||
TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed});
|
TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed});
|
||||||
|
|
||||||
|
@ -221,7 +221,8 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
const std::string expected = R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
|
const std::string expected =
|
||||||
|
R"(Type '{ t: { v: string } }' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
|
||||||
|
|
||||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
|
@ -10,7 +10,6 @@ using namespace Luau;
|
|||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||||
LUAU_FASTFLAG(LuauSetMetatableOnUnionsOfTables);
|
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("BuiltinTests");
|
TEST_SUITE_BEGIN("BuiltinTests");
|
||||||
|
|
||||||
@ -371,8 +370,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{FFlag::LuauSetMetatableOnUnionsOfTables, true};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type A = {tag: "A", x: number}
|
type A = {tag: "A", x: number}
|
||||||
type B = {tag: "B", y: string}
|
type B = {tag: "B", y: string}
|
||||||
|
@ -648,32 +648,20 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties")
|
|||||||
unfreeze(arena);
|
unfreeze(arena);
|
||||||
|
|
||||||
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
getMutable<ClassType>(instanceType)->props = {
|
getMutable<ClassType>(instanceType)->props = {{"Parent", Property::rw(instanceType)}};
|
||||||
{"Parent", Property::rw(instanceType)}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
|
TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||||
|
|
||||||
TypeId scriptType = arena.addType(ClassType{
|
TypeId scriptType =
|
||||||
"Script", {
|
arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test"});
|
||||||
{"Parent", Property::rw(workspaceType, instanceType)}
|
|
||||||
},
|
|
||||||
instanceType, nullopt, {}, {}, "Test"
|
|
||||||
});
|
|
||||||
|
|
||||||
TypeId partType = arena.addType(ClassType{
|
TypeId partType = arena.addType(
|
||||||
"Part", {
|
ClassType{"Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}},
|
||||||
{"BrickColor", Property::rw(builtinTypes->stringType)},
|
instanceType, nullopt, {}, {}, "Test"});
|
||||||
{"Parent", Property::rw(workspaceType, instanceType)}
|
|
||||||
},
|
|
||||||
instanceType, nullopt, {}, {}, "Test"});
|
|
||||||
|
|
||||||
getMutable<ClassType>(workspaceType)->props = {
|
getMutable<ClassType>(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}};
|
||||||
{"Script", Property::readonly(scriptType)},
|
|
||||||
{"Part", Property::readonly(partType)}
|
|
||||||
};
|
|
||||||
|
|
||||||
frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType};
|
frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType};
|
||||||
|
|
||||||
@ -703,7 +691,8 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer")
|
|||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
CHECK_MESSAGE(get<DynamicPropertyLookupOnClassesUnsafe>(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]);
|
CHECK_MESSAGE(
|
||||||
|
get<DynamicPropertyLookupOnClassesUnsafe>(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0]);
|
||||||
|
|
||||||
CHECK(builtinTypes->errorType == requireType("c"));
|
CHECK(builtinTypes->errorType == requireType("c"));
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|||||||
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit);
|
LUAU_FASTINT(LuauTarjanChildLimit);
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks")
|
TEST_CASE_FIXTURE(Fixture, "general_case_table_literal_blocks")
|
||||||
@ -2129,10 +2131,20 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables")
|
|||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
|
{
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
{
|
||||||
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function { x: number } & { y: string }");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Cannot call non-function {| x: number |}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod")
|
||||||
@ -2535,4 +2547,54 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_definition_in_a_do_block_with_globa
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "fuzzer_alias_global_function_doesnt_hit_nil_assert")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function _()
|
||||||
|
end
|
||||||
|
local function l0()
|
||||||
|
function _()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
_ = _
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "fuzzer_bug_missing_follow_causes_assertion")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local _ = ({_=function()
|
||||||
|
return _
|
||||||
|
end,}),true,_[_()]
|
||||||
|
for l0=_[_[_[`{function(l0)
|
||||||
|
end}`]]],_[_.n6[_[_.n6]]],_[_[_.n6[_[_.n6]]]] do
|
||||||
|
_ += if _ then ""
|
||||||
|
end
|
||||||
|
return _
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "cannot_call_union_of_functions")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local f: (() -> ()) | (() -> () -> ()) = nil :: any
|
||||||
|
f()
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
|
{
|
||||||
|
std::string expected = R"(Cannot call a value of the union type:
|
||||||
|
| () -> ()
|
||||||
|
| () -> () -> ()
|
||||||
|
We are unable to determine the appropriate result type for such a call.)";
|
||||||
|
|
||||||
|
CHECK(expected == toString(result.errors[0]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
CHECK("Cannot call non-function (() -> () -> ()) | (() -> ())" == toString(result.errors[0]));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -17,6 +17,8 @@ using namespace Luau;
|
|||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties)
|
LUAU_FASTFLAG(LuauOkWithIteratingOverTableProperties)
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TypeInferLoops");
|
TEST_SUITE_BEGIN("TypeInferLoops");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "for_loop")
|
TEST_CASE_FIXTURE(Fixture, "for_loop")
|
||||||
@ -165,7 +167,11 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_should_fail_with_non_function_iterator")
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
|
|
||||||
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
|
CHECK_EQ("Cannot call a value of type string", toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
CHECK_EQ("Cannot call non-function string", toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok")
|
||||||
|
@ -338,7 +338,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_prop")
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
@ -592,7 +592,10 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
CHECK_EQ(toString(requireTypeAtPosition({3, 28})), "number | string"); // a ~= nil
|
||||||
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "nil"); // a == nil :)
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(requireTypeAtPosition({5, 28})), "(number | string)?"); // a == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
||||||
|
@ -22,6 +22,9 @@ LUAU_FASTFLAG(LuauAlwaysCommitInferencesOfFunctionCalls);
|
|||||||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
|
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering);
|
||||||
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
LUAU_FASTFLAG(DebugLuauSharedSelf);
|
||||||
LUAU_FASTFLAG(LuauReadWritePropertySyntax);
|
LUAU_FASTFLAG(LuauReadWritePropertySyntax);
|
||||||
|
LUAU_FASTFLAG(LuauMetatableInstantiationCloneCheck);
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauImproveNonFunctionCallError)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("TableTests");
|
TEST_SUITE_BEGIN("TableTests");
|
||||||
|
|
||||||
@ -2383,7 +2386,11 @@ b()
|
|||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
|
||||||
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
|
CHECK_EQ(toString(result.errors[0]), R"(Cannot call a value of type t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||||
|
else
|
||||||
|
CHECK_EQ(toString(result.errors[0]), R"(Cannot call non-function t1 where t1 = { @metatable { __call: t1 }, { } })");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
TEST_CASE_FIXTURE(Fixture, "table_subtyping_shouldn't_add_optional_properties_to_sealed_tables")
|
||||||
@ -3016,7 +3023,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_call_metamethod_must_be_callable")
|
|||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
if (DFFlag::LuauImproveNonFunctionCallError)
|
||||||
|
CHECK("Cannot call a value of type { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
||||||
|
else
|
||||||
|
CHECK("Cannot call non-function { @metatable { __call: number }, { } }" == toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -3994,9 +4004,10 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
|
|||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
std::string expected = "Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
std::string expected =
|
||||||
"\n\tat [read \"b\"], boolean is not exactly string"
|
"Type '{ a: string, b: boolean, c: number }' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
||||||
"\n\tat [read \"c\"], number is not exactly boolean";
|
"\n\tat [read \"b\"], boolean is not exactly string"
|
||||||
|
"\n\tat [read \"c\"], number is not exactly boolean";
|
||||||
CHECK(toString(result.errors[0]) == expected);
|
CHECK(toString(result.errors[0]) == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4144,10 +4155,7 @@ TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
|
||||||
{FFlag::LuauReadWritePropertySyntax, true},
|
|
||||||
{FFlag::DebugLuauDeferredConstraintResolution, false}
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type W = {read x: number}
|
type W = {read x: number}
|
||||||
@ -4171,10 +4179,7 @@ TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, false}};
|
||||||
{FFlag::LuauReadWritePropertySyntax, true},
|
|
||||||
{FFlag::DebugLuauDeferredConstraintResolution, false}
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type T = {read [string]: number}
|
type T = {read [string]: number}
|
||||||
@ -4191,10 +4196,7 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff[] = {
|
ScopedFastFlag sff[] = {{FFlag::LuauReadWritePropertySyntax, true}, {FFlag::DebugLuauDeferredConstraintResolution, true}};
|
||||||
{FFlag::LuauReadWritePropertySyntax, true},
|
|
||||||
{FFlag::DebugLuauDeferredConstraintResolution, true}
|
|
||||||
};
|
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
function oc(player, speaker)
|
function oc(player, speaker)
|
||||||
@ -4206,8 +4208,8 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
|||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK("<a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () "
|
CHECK("<a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () "
|
||||||
"where "
|
"where "
|
||||||
"t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")));
|
"t1 = { read FindFirstChild: (t1, string) -> (a, b...) }" == toString(requireType("oc")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "tables_can_have_both_metatables_and_indexers")
|
||||||
@ -4359,4 +4361,47 @@ TEST_CASE_FIXTURE(Fixture, "setindexer_always_transmute")
|
|||||||
CHECK_EQ("(*error-type*) -> ()", toString(requireType("f")));
|
CHECK_EQ("(*error-type*) -> ()", toString(requireType("f")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiated_metatable_frozen_table_clone_mutation")
|
||||||
|
{
|
||||||
|
ScopedFastFlag luauMetatableInstantiationCloneCheck{FFlag::LuauMetatableInstantiationCloneCheck, true};
|
||||||
|
|
||||||
|
fileResolver.source["game/worker"] = R"(
|
||||||
|
type WorkerImpl<T..., R...> = {
|
||||||
|
destroy: (self: Worker<T..., R...>) -> boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkerProps = { id: number }
|
||||||
|
|
||||||
|
export type Worker<T..., R...> = typeof(setmetatable({} :: WorkerProps, {} :: WorkerImpl<T..., R...>))
|
||||||
|
|
||||||
|
return {}
|
||||||
|
)";
|
||||||
|
|
||||||
|
fileResolver.source["game/library"] = R"(
|
||||||
|
local Worker = require(game.worker)
|
||||||
|
|
||||||
|
export type Worker<T..., R...> = Worker.Worker<T..., R...>
|
||||||
|
|
||||||
|
return {}
|
||||||
|
)";
|
||||||
|
|
||||||
|
CheckResult result = frontend.check("game/library");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function f(t: { [string]: number } & { [thread]: boolean }, x)
|
||||||
|
local k = "a"
|
||||||
|
t[k] = x
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK("({ [string]: number } & { [thread]: boolean }, boolean | number) -> ()" == toString(requireType("f")));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
@ -986,7 +986,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_found_this")
|
|||||||
*/
|
*/
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
|
||||||
{
|
{
|
||||||
(void) check(R"(
|
(void)check(R"(
|
||||||
local _
|
local _
|
||||||
if _ then
|
if _ then
|
||||||
_ = _
|
_ = _
|
||||||
@ -999,7 +999,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_found_this_2")
|
|||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "indexing_a_cyclic_intersection_does_not_crash")
|
TEST_CASE_FIXTURE(Fixture, "indexing_a_cyclic_intersection_does_not_crash")
|
||||||
{
|
{
|
||||||
(void) check(R"(
|
(void)check(R"(
|
||||||
local _
|
local _
|
||||||
if _ then
|
if _ then
|
||||||
while nil do
|
while nil do
|
||||||
|
@ -200,7 +200,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assignment_swap")
|
|||||||
CHECK("number" == toString(requireType("b")));
|
CHECK("number" == toString(requireType("b")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types")
|
TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types_2")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function f(x): number?
|
local function f(x): number?
|
||||||
|
@ -12,6 +12,25 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
|||||||
|
|
||||||
TEST_SUITE_BEGIN("UnionTypes");
|
TEST_SUITE_BEGIN("UnionTypes");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local _ = {},nil
|
||||||
|
repeat
|
||||||
|
|
||||||
|
_,_ = if _.number == "" or _.number or _._ then
|
||||||
|
_
|
||||||
|
elseif _.__index == _._G then
|
||||||
|
tostring
|
||||||
|
elseif _ then
|
||||||
|
_
|
||||||
|
else
|
||||||
|
``,_._G
|
||||||
|
|
||||||
|
until _._
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
|
TEST_CASE_FIXTURE(Fixture, "return_types_can_be_disjoint")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
@ -572,7 +591,8 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
|||||||
UnionType u;
|
UnionType u;
|
||||||
|
|
||||||
u.options.push_back(badCyclicUnionTy);
|
u.options.push_back(badCyclicUnionTy);
|
||||||
u.options.push_back(arena.addType(TableType{{}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed}));
|
u.options.push_back(arena.addType(TableType{
|
||||||
|
{}, TableIndexer{builtinTypes->numberType, builtinTypes->numberType}, TypeLevel{}, frontend.globals.globalScope.get(), TableState::Sealed}));
|
||||||
|
|
||||||
asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u));
|
asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u));
|
||||||
|
|
||||||
|
@ -64,6 +64,39 @@ do
|
|||||||
|
|
||||||
a = table.move({[minI] = 100}, minI, minI, maxI)
|
a = table.move({[minI] = 100}, minI, minI, maxI)
|
||||||
eqT(a, {[minI] = 100, [maxI] = 100})
|
eqT(a, {[minI] = 100, [maxI] = 100})
|
||||||
|
|
||||||
|
-- moving small amount of elements (array/hash) using a wide range
|
||||||
|
a = {}
|
||||||
|
table.move({1, 2, 3, 4, 5}, -100000000, 100000000, -100000000, a)
|
||||||
|
eqT(a, {1, 2, 3, 4, 5})
|
||||||
|
|
||||||
|
a = {}
|
||||||
|
table.move({1, 2}, -100000000, 100000000, 0, a)
|
||||||
|
eqT(a, {[100000001] = 1, [100000002] = 2})
|
||||||
|
|
||||||
|
-- hash part copy
|
||||||
|
a = {}
|
||||||
|
table.move({[-1000000] = 1, [-100] = 2, [100] = 3, [100000] = 4}, -100000000, 100000000, 0, a)
|
||||||
|
eqT(a, {[99000000] = 1, [99999900] = 2, [100000100] = 3, [100100000] = 4})
|
||||||
|
|
||||||
|
-- precise hash part bounds
|
||||||
|
a = {}
|
||||||
|
table.move({[-100000000 - 1] = -1, [-100000000] = 1, [-100] = 2, [100] = 3, [100000000] = 4, [100000000 + 1] = -1}, -100000000, 100000000, 0, a)
|
||||||
|
eqT(a, {[0] = 1, [99999900] = 2, [100000100] = 3, [200000000] = 4})
|
||||||
|
|
||||||
|
-- no integer undeflow in corner hash part case
|
||||||
|
a = {}
|
||||||
|
table.move({[minI] = 100, [-100] = 2}, minI, minI + 100000000, minI, a)
|
||||||
|
eqT(a, {[minI] = 100})
|
||||||
|
|
||||||
|
-- hash part skips array slice
|
||||||
|
a = {}
|
||||||
|
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4}, -1, 3, 1, a)
|
||||||
|
eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4})
|
||||||
|
|
||||||
|
a = {}
|
||||||
|
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4, [10] = 5, [100] = 6, [1000] = 7}, -1, 3, 1, a)
|
||||||
|
eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4})
|
||||||
end
|
end
|
||||||
|
|
||||||
checkerror("too many", table.move, {}, 0, maxI, 1)
|
checkerror("too many", table.move, {}, 0, maxI, 1)
|
||||||
|
@ -49,8 +49,6 @@ GenericsTests.bound_tables_do_not_clone_original_fields
|
|||||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||||
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
||||||
GenericsTests.do_not_infer_generic_functions
|
GenericsTests.do_not_infer_generic_functions
|
||||||
GenericsTests.dont_leak_generic_types
|
|
||||||
GenericsTests.dont_leak_inferred_generic_types
|
|
||||||
GenericsTests.dont_substitute_bound_types
|
GenericsTests.dont_substitute_bound_types
|
||||||
GenericsTests.error_detailed_function_mismatch_generic_pack
|
GenericsTests.error_detailed_function_mismatch_generic_pack
|
||||||
GenericsTests.error_detailed_function_mismatch_generic_types
|
GenericsTests.error_detailed_function_mismatch_generic_types
|
||||||
@ -155,7 +153,6 @@ RefinementTest.x_is_not_instance_or_else_not_part
|
|||||||
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
TableTests.a_free_shape_can_turn_into_a_scalar_directly
|
||||||
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||||
TableTests.accidentally_checked_prop_in_opposite_branch
|
|
||||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||||
TableTests.array_factory_function
|
TableTests.array_factory_function
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||||
@ -395,6 +392,8 @@ TypeInferOperators.UnknownGlobalCompoundAssign
|
|||||||
TypeInferPrimitives.CheckMethodsOfNumber
|
TypeInferPrimitives.CheckMethodsOfNumber
|
||||||
TypeInferPrimitives.string_index
|
TypeInferPrimitives.string_index
|
||||||
TypeInferUnknownNever.assign_to_local_which_is_never
|
TypeInferUnknownNever.assign_to_local_which_is_never
|
||||||
|
TypeInferUnknownNever.compare_never
|
||||||
|
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
|
||||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||||
TypeInferUnknownNever.length_of_never
|
TypeInferUnknownNever.length_of_never
|
||||||
@ -412,7 +411,6 @@ TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
|||||||
TypeSingletons.return_type_of_f_is_not_widened
|
TypeSingletons.return_type_of_f_is_not_widened
|
||||||
TypeSingletons.table_properties_type_error_escapes
|
TypeSingletons.table_properties_type_error_escapes
|
||||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||||
TypeStatesTest.prototyped_recursive_functions_but_has_future_assignments
|
|
||||||
TypeStatesTest.typestates_preserve_error_suppression_properties
|
TypeStatesTest.typestates_preserve_error_suppression_properties
|
||||||
UnionTypes.error_detailed_optional
|
UnionTypes.error_detailed_optional
|
||||||
UnionTypes.error_detailed_union_all
|
UnionTypes.error_detailed_union_all
|
||||||
|
Loading…
Reference in New Issue
Block a user