mirror of
https://github.com/luau-lang/luau.git
synced 2025-08-26 03:17:04 +01:00
Sync to upstream/release/687 (#1954)
## What's Changed? This week we have an update with an implementation for one of the RFCs we had approved before, an improvement of the new type solver and a small Lua 5.1 C API compatibility improvement. * `@deprecated` attribute can now have a custom suggestion for a replacement and a reason message as described in [deprecated attribute parameters RFC](https://rfcs.luau.org/syntax-attribute-functions-deprecated.html) For example: ```luau @[deprecated {reason = "foo suffers from performance issues", use = "bar"}] local function foo() ... end -- Function 'foo' is deprecated, use 'bar' instead. foo suffers from performance issues foo() ``` * `lua_cpcall` C API function has been restored both for compatibility with Lua 5.1 and as a safe way to enter protected call environment to work with Luau C API functions that may error Instead of ``` if (!lua_checkstack(L, 2)) return -1; lua_pushcfunction(L, test, nullptr); lua_pushlightuserdata(L, context); int status = lua_pcall(L, 1, 0, 0); ``` you can simply do ``` int status = lua_cpcall(L, test, context); ``` * In Luau CLI, required module return values can now have any type ## New Type Solver - Additional improvements on type refinements used with external types should fix some reported false positive errors where types refined to `never` - Fixed an issue in recursive refinement types in a form of `t1 where t1 = refine<t1, _>` getting 'stuck' - Fixed an issue in subtyping of generic functions, it is now possible to assign `<T>(T, (T) -> T) -> T` to `(number, <X>(X) -> X) -> number` - Fixed an ICE caused by recursive types (Fixes #1686) - Added additional iteration and recursion limits to stop the type solver before system resources are used up ## Internal Contributors Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Annie Tang <annietang@roblox.com> Co-authored-by: Ariel Weiss <aaronweiss@roblox.com> Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com> Co-authored-by: Ilya Rezvov <irezvov@roblox.com> Co-authored-by: Sora Kanosue <skanosue@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
8863bfc950
commit
55f3e00938
99 changed files with 4050 additions and 1436 deletions
|
@ -52,6 +52,7 @@ struct GeneralizationConstraint
|
|||
|
||||
std::vector<TypeId> interiorTypes;
|
||||
bool hasDeprecatedAttribute = false;
|
||||
AstAttr::DeprecatedInfo deprecatedInfo;
|
||||
|
||||
/// If true, never introduce generics. Always replace free types by their
|
||||
/// bounds or unknown. Presently used only to generalize the whole module.
|
||||
|
|
|
@ -504,6 +504,15 @@ struct MultipleNonviableOverloads
|
|||
bool operator==(const MultipleNonviableOverloads& rhs) const;
|
||||
};
|
||||
|
||||
// Error where a type alias violates the recursive restraint, ie when a type alias T<A> has T with different arguments on the RHS.
|
||||
struct RecursiveRestraintViolation
|
||||
{
|
||||
bool operator==(const RecursiveRestraintViolation& rhs) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
using TypeErrorData = Variant<
|
||||
TypeMismatch,
|
||||
UnknownSymbol,
|
||||
|
@ -559,7 +568,8 @@ using TypeErrorData = Variant<
|
|||
CannotCheckDynamicStringFormatCalls,
|
||||
GenericTypeCountMismatch,
|
||||
GenericTypePackCountMismatch,
|
||||
MultipleNonviableOverloads>;
|
||||
MultipleNonviableOverloads,
|
||||
RecursiveRestraintViolation>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
|
|
@ -176,7 +176,7 @@ struct Frontend
|
|||
|
||||
Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {});
|
||||
|
||||
void setLuauSolverSelectionFromWorkspace(SolverMode mode);
|
||||
void setLuauSolverMode(SolverMode mode);
|
||||
SolverMode getLuauSolverMode() const;
|
||||
// The default value assuming there is no workspace setup yet
|
||||
std::atomic<SolverMode> useNewLuauSolver{FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old};
|
||||
|
@ -330,7 +330,7 @@ ModulePtr check(
|
|||
NotNull<InternalErrorReporter> iceHandler,
|
||||
NotNull<ModuleResolver> moduleResolver,
|
||||
NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope,
|
||||
const ScopePtr& parentScope,
|
||||
const ScopePtr& typeFunctionScope,
|
||||
std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope,
|
||||
FrontendOptions options,
|
||||
|
|
|
@ -102,6 +102,10 @@ struct Scope
|
|||
std::optional<std::vector<TypeId>> interiorFreeTypes;
|
||||
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
|
||||
|
||||
// A set of type alias names that are invalid because they violate the recursion restrictions of type aliases.
|
||||
DenseHashSet<std::string> invalidTypeAliasNames{""};
|
||||
bool isInvalidTypeAliasName(const std::string& name) const;
|
||||
|
||||
NotNull<Scope> findNarrowestScopeContaining(Location);
|
||||
};
|
||||
|
||||
|
|
|
@ -66,6 +66,13 @@ struct TarjanWorklistVertex
|
|||
int index;
|
||||
int currEdge;
|
||||
int lastEdge;
|
||||
|
||||
TarjanWorklistVertex(int index, int currEdge, int lastEdge)
|
||||
: index(index)
|
||||
, currEdge(currEdge)
|
||||
, lastEdge(lastEdge)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct TarjanNode
|
||||
|
@ -79,6 +86,15 @@ struct TarjanNode
|
|||
// Tarjan calculates the lowlink for each vertex,
|
||||
// which is the lowest ancestor index reachable from the vertex.
|
||||
int lowlink;
|
||||
|
||||
TarjanNode(TypeId ty, TypePackId tp, bool onStack, bool dirty, int lowlink)
|
||||
: ty(ty)
|
||||
, tp(tp)
|
||||
, onStack(onStack)
|
||||
, dirty(dirty)
|
||||
, lowlink(lowlink)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Tarjan's algorithm for finding the SCCs in a cyclic structure.
|
||||
|
|
|
@ -88,6 +88,13 @@ struct SubtypingResult
|
|||
struct SubtypingEnvironment
|
||||
{
|
||||
struct GenericBounds
|
||||
{
|
||||
TypeIds lowerBound;
|
||||
TypeIds upperBound;
|
||||
};
|
||||
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
struct GenericBounds_DEPRECATED
|
||||
{
|
||||
DenseHashSet<TypeId> lowerBound{nullptr};
|
||||
DenseHashSet<TypeId> upperBound{nullptr};
|
||||
|
@ -98,22 +105,36 @@ struct SubtypingEnvironment
|
|||
|
||||
/// Applies `mappedGenerics` to the given type.
|
||||
/// This is used specifically to substitute for generics in type function instances.
|
||||
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
||||
std::optional<TypeId> applyMappedGenerics(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
TypeId ty,
|
||||
NotNull<InternalErrorReporter> iceReporter
|
||||
);
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
std::optional<TypeId> applyMappedGenerics_DEPRECATED(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
||||
|
||||
const TypeId* tryFindSubstitution(TypeId ty) const;
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
const SubtypingResult* tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const;
|
||||
|
||||
bool containsMappedType(TypeId ty) const;
|
||||
bool containsMappedPack(TypePackId tp) const;
|
||||
|
||||
GenericBounds& getMappedTypeBounds(TypeId ty);
|
||||
GenericBounds& getMappedTypeBounds(TypeId ty, NotNull<InternalErrorReporter> iceReporter);
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
GenericBounds_DEPRECATED& getMappedTypeBounds_DEPRECATED(TypeId ty);
|
||||
TypePackId* getMappedPackBounds(TypePackId tp);
|
||||
|
||||
/*
|
||||
* When we encounter a generic over the course of a subtyping test, we need
|
||||
* to tentatively map that generic onto a type on the other side.
|
||||
* to tentatively map that generic onto a type on the other side. We map to a
|
||||
* vector of bounds, since generics may be shadowed by nested types. The back
|
||||
* of each vector represents the current scope.
|
||||
*/
|
||||
DenseHashMap<TypeId, GenericBounds> mappedGenerics{nullptr};
|
||||
DenseHashMap<TypeId, std::vector<GenericBounds>> mappedGenerics{nullptr};
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
DenseHashMap<TypeId, GenericBounds_DEPRECATED> mappedGenerics_DEPRECATED{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
/*
|
||||
|
@ -124,7 +145,14 @@ struct SubtypingEnvironment
|
|||
*/
|
||||
DenseHashMap<TypeId, TypeId> substitutions{nullptr};
|
||||
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
||||
|
||||
// We use this cache to track pairs of subtypes that we tried to subtype, and found them to be in the seen set at the time.
|
||||
// In those situations, we return True, but mark the result as not cacheable, because we don't want to cache broader results which
|
||||
// led to the seen pair. However, those results were previously being cache in the ephemeralCache, and we still want to cache them somewhere
|
||||
// for performance reasons.
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> seenSetCache{{}};
|
||||
};
|
||||
|
||||
struct Subtyping
|
||||
|
@ -144,6 +172,7 @@ struct Subtyping
|
|||
Contravariant
|
||||
};
|
||||
|
||||
// TODO: Clip this along with LuauSubtypingGenericsDoesntUseVariance?
|
||||
Variance variance = Variance::Covariant;
|
||||
|
||||
using SeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
|
||||
|
@ -178,7 +207,12 @@ struct Subtyping
|
|||
// TODO recursion limits
|
||||
|
||||
SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope);
|
||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope);
|
||||
SubtypingResult isSubtype(
|
||||
TypePackId subTp,
|
||||
TypePackId superTp,
|
||||
NotNull<Scope> scope,
|
||||
std::optional<std::vector<TypeId>> bindableGenerics = std::nullopt
|
||||
);
|
||||
|
||||
private:
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> resultCache{{}};
|
||||
|
@ -323,6 +357,16 @@ private:
|
|||
TypeId superTy,
|
||||
NotNull<Scope> scope,
|
||||
SubtypingResult& original);
|
||||
|
||||
SubtypingResult checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull<Scope> scope);
|
||||
|
||||
static void maybeUpdateBounds(
|
||||
TypeId here,
|
||||
TypeId there,
|
||||
TypeIds& boundsToUpdate,
|
||||
const TypeIds& firstBoundsToCheck,
|
||||
const TypeIds& secondBoundsToCheck
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -7,11 +7,9 @@ namespace Luau
|
|||
|
||||
enum class SubtypingVariance
|
||||
{
|
||||
// Used for an empty key. Should never appear in actual code.
|
||||
// Useful for an empty hash table key. Should never arise from actual code.
|
||||
Invalid,
|
||||
Covariant,
|
||||
// This is used to identify cases where we have a covariant + a
|
||||
// contravariant reason and we need to merge them.
|
||||
Contravariant,
|
||||
Invariant,
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/ParseOptions.h"
|
||||
#include "Luau/ParseResult.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -24,6 +25,7 @@ void dump(AstNode* node);
|
|||
// Never fails on a well-formed AST
|
||||
std::string transpile(AstStatBlock& ast);
|
||||
std::string transpileWithTypes(AstStatBlock& block);
|
||||
std::string transpileWithTypes(AstStatBlock &block, const CstNodeMap& cstNodeMap);
|
||||
|
||||
// Only fails when parsing fails
|
||||
TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false);
|
||||
|
|
|
@ -391,6 +391,7 @@ struct FunctionType
|
|||
bool hasNoFreeOrGenericTypes = false;
|
||||
bool isCheckedFunction = false;
|
||||
bool isDeprecatedFunction = false;
|
||||
std::shared_ptr<AstAttr::DeprecatedInfo> deprecatedInfo;
|
||||
};
|
||||
|
||||
enum class TableState
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
bool empty() const;
|
||||
size_t count(TypeId ty) const;
|
||||
|
||||
void reserve(size_t n);
|
||||
|
||||
template<class Iterator>
|
||||
void insert(Iterator begin, Iterator end)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Luau/Error.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeIds.h"
|
||||
#include "Luau/TypePack.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -363,5 +364,38 @@ inline constexpr char kLuauForceConstraintSolvingIncomplete[] = "_luau_force_con
|
|||
// not get emplaced by constraint solving.
|
||||
inline constexpr char kLuauBlockedType[] = "_luau_blocked_type";
|
||||
|
||||
struct UnionBuilder
|
||||
{
|
||||
UnionBuilder(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
|
||||
void add(TypeId ty);
|
||||
TypeId build();
|
||||
size_t size() const;
|
||||
void reserve(size_t size);
|
||||
|
||||
private:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
TypeIds options;
|
||||
bool isTop = false;
|
||||
};
|
||||
|
||||
struct IntersectionBuilder
|
||||
{
|
||||
IntersectionBuilder(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes);
|
||||
void add(TypeId ty);
|
||||
TypeId build();
|
||||
size_t size() const;
|
||||
void reserve(size_t size);
|
||||
|
||||
private:
|
||||
NotNull<TypeArena> arena;
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
TypeIds parts;
|
||||
bool isBottom = false;
|
||||
};
|
||||
|
||||
TypeId addIntersection(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, std::initializer_list<TypeId> list);
|
||||
TypeId addUnion(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, std::initializer_list<TypeId> list);
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -26,6 +26,27 @@ enum class OccursCheckResult
|
|||
Fail
|
||||
};
|
||||
|
||||
enum class UnifyResult
|
||||
{
|
||||
Ok,
|
||||
OccursCheckFailed,
|
||||
TooComplex
|
||||
};
|
||||
|
||||
inline UnifyResult operator &(UnifyResult lhs, UnifyResult rhs)
|
||||
{
|
||||
if (lhs == UnifyResult::Ok)
|
||||
return rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline UnifyResult& operator&=(UnifyResult& lhs, UnifyResult rhs)
|
||||
{
|
||||
if (lhs == UnifyResult::Ok)
|
||||
lhs = rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
struct Unifier2
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
|
@ -50,6 +71,7 @@ struct Unifier2
|
|||
std::vector<TypeId> newFreshTypes;
|
||||
std::vector<TypePackId> newFreshTypePacks;
|
||||
|
||||
int iterationCount = 0;
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
|
||||
|
@ -66,6 +88,10 @@ struct Unifier2
|
|||
DenseHashSet<const void*>* uninhabitedTypeFunctions
|
||||
);
|
||||
|
||||
UnifyResult unify(TypeId subTy, TypeId superTy);
|
||||
UnifyResult unify(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
private:
|
||||
/** Attempt to commit the subtype relation subTy <: superTy to the type
|
||||
* graph.
|
||||
*
|
||||
|
@ -78,30 +104,28 @@ struct Unifier2
|
|||
* Presently, the only way unification can fail is if we attempt to bind one
|
||||
* free TypePack to another and encounter an occurs check violation.
|
||||
*/
|
||||
bool unify(TypeId subTy, TypeId superTy);
|
||||
bool unifyFreeWithType(TypeId subTy, TypeId superTy);
|
||||
bool unify(TypeId subTy, const FunctionType* superFn);
|
||||
bool unify(const UnionType* subUnion, TypeId superTy);
|
||||
bool unify(TypeId subTy, const UnionType* superUnion);
|
||||
bool unify(const IntersectionType* subIntersection, TypeId superTy);
|
||||
bool unify(TypeId subTy, const IntersectionType* superIntersection);
|
||||
bool unify(TableType* subTable, const TableType* superTable);
|
||||
bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable);
|
||||
UnifyResult unify_(TypeId subTy, TypeId superTy);
|
||||
UnifyResult unifyFreeWithType(TypeId subTy, TypeId superTy);
|
||||
UnifyResult unify_(TypeId subTy, const FunctionType* superFn);
|
||||
UnifyResult unify_(const UnionType* subUnion, TypeId superTy);
|
||||
UnifyResult unify_(TypeId subTy, const UnionType* superUnion);
|
||||
UnifyResult unify_(const IntersectionType* subIntersection, TypeId superTy);
|
||||
UnifyResult unify_(TypeId subTy, const IntersectionType* superIntersection);
|
||||
UnifyResult unify_(TableType* subTable, const TableType* superTable);
|
||||
UnifyResult unify_(const MetatableType* subMetatable, const MetatableType* superMetatable);
|
||||
|
||||
bool unify(const AnyType* subAny, const FunctionType* superFn);
|
||||
bool unify(const FunctionType* subFn, const AnyType* superAny);
|
||||
bool unify(const AnyType* subAny, const TableType* superTable);
|
||||
bool unify(const TableType* subTable, const AnyType* superAny);
|
||||
UnifyResult unify_(const AnyType* subAny, const FunctionType* superFn);
|
||||
UnifyResult unify_(const FunctionType* subFn, const AnyType* superAny);
|
||||
UnifyResult unify_(const AnyType* subAny, const TableType* superTable);
|
||||
UnifyResult unify_(const TableType* subTable, const AnyType* superAny);
|
||||
|
||||
bool unify(const MetatableType* subMetatable, const AnyType*);
|
||||
bool unify(const AnyType*, const MetatableType* superMetatable);
|
||||
UnifyResult unify_(const MetatableType* subMetatable, const AnyType*);
|
||||
UnifyResult unify_(const AnyType*, const MetatableType* superMetatable);
|
||||
|
||||
// TODO think about this one carefully. We don't do unions or intersections of type packs
|
||||
bool unify(TypePackId subTp, TypePackId superTp);
|
||||
UnifyResult unify_(TypePackId subTp, TypePackId superTp);
|
||||
|
||||
std::optional<TypeId> generalize(TypeId ty);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @returns simplify(left | right)
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticVisitType)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -218,6 +219,20 @@ struct GenericTypeVisitor
|
|||
|
||||
void traverse(TypeId ty)
|
||||
{
|
||||
// Morally, if `skipBoundTypes` is set, then whenever we
|
||||
// encounter a bound type we should "skip" ahead to the first
|
||||
// non-bound type. This helps keep stack pressure in check
|
||||
// while using bound types instead of mutating types in place
|
||||
// elsewhere (such as in generalization).
|
||||
//
|
||||
// We do this check here such that we now will now treat all
|
||||
// bound types as if they're direct pointers to some final
|
||||
// non-bound type. If we do the check later, then we might
|
||||
// get slightly different behavior depending on the exact
|
||||
// entry point for cyclic types.
|
||||
if (FFlag::LuauReduceSetTypeStackPressure && is<BoundType>(ty) && skipBoundTypes)
|
||||
ty = follow(ty);
|
||||
|
||||
RecursionLimiter limiter{visitorName, &recursionCounter, FInt::LuauVisitRecursionLimit};
|
||||
|
||||
if (visit_detail::hasSeen(seen, ty))
|
||||
|
@ -228,10 +243,21 @@ struct GenericTypeVisitor
|
|||
|
||||
if (auto btv = get<BoundType>(ty))
|
||||
{
|
||||
if (skipBoundTypes)
|
||||
traverse(btv->boundTo);
|
||||
else if (visit(ty, *btv))
|
||||
traverse(btv->boundTo);
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
// At this point, we know that `skipBoundTypes` is false, as
|
||||
// otherwise we would have hit the above branch.
|
||||
LUAU_ASSERT(!skipBoundTypes);
|
||||
if (visit(ty, *btv))
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skipBoundTypes)
|
||||
traverse(btv->boundTo);
|
||||
else if (visit(ty, *btv))
|
||||
traverse(btv->boundTo);
|
||||
}
|
||||
}
|
||||
else if (auto ftv = get<FreeType>(ty))
|
||||
{
|
||||
|
|
|
@ -1331,7 +1331,7 @@ static bool autocompleteIfElseExpression(
|
|||
if (node->is<AstExprIfElse>())
|
||||
{
|
||||
// Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else
|
||||
// expression.
|
||||
// expression).
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -193,18 +193,29 @@ TypeId makeFunction(
|
|||
FunctionType ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()};
|
||||
|
||||
if (selfType)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}});
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv.argNames.emplace_back(Luau::FunctionArgument{"self", {}});
|
||||
else
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{"self", {}});
|
||||
}
|
||||
|
||||
if (paramNames.size() != 0)
|
||||
{
|
||||
for (auto&& p : paramNames)
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv.argNames.emplace_back(Luau::FunctionArgument{p, Location{}});
|
||||
else
|
||||
ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}});
|
||||
}
|
||||
else if (selfType)
|
||||
{
|
||||
// If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well
|
||||
for (size_t i = 0; i < paramTypes.size(); i++)
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv.argNames.emplace_back(std::nullopt);
|
||||
else
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
|
||||
ftv.isCheckedFunction = checked;
|
||||
|
@ -384,7 +395,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
|||
|
||||
TypeId genericT = arena.addType(GenericType{globalScope, "T"});
|
||||
|
||||
if ((frontend.getLuauSolverMode() == SolverMode::New) && FFlag::LuauUpdateGetMetatableTypeSignature)
|
||||
if (frontend.getLuauSolverMode() == SolverMode::New)
|
||||
{
|
||||
// getmetatable : <T>(T) -> getmetatable<T>
|
||||
TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}});
|
||||
|
|
|
@ -17,17 +17,16 @@
|
|||
#include "Luau/UserDefinedTypeFunction.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
|
||||
LUAU_FASTFLAG(LuauOccursCheckForRefinement)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotBlockOnStuckTypeFunctions)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineOccursCheckDirectRecursion)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -231,7 +230,7 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
|
|||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
||||
|
@ -314,7 +313,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
|
|||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
||||
|
@ -670,7 +669,7 @@ TypeFunctionReductionResult<TypeId> concatTypeFunction(
|
|||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
||||
|
@ -919,7 +918,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
|
|||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
||||
|
@ -1048,7 +1047,7 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
|
|||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice};
|
||||
|
@ -1094,7 +1093,6 @@ struct ContainsRefinableType : TypeOnceVisitor
|
|||
{
|
||||
}
|
||||
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
// Default case: if we find *some* type that's worth refining against,
|
||||
|
@ -1189,7 +1187,7 @@ struct RefineTypeScrubber : public Substitution
|
|||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return FFlag::LuauRefineOccursCheckDirectRecursion ? ty == needle : false;
|
||||
}
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
|
@ -1231,7 +1229,10 @@ struct RefineTypeScrubber : public Substitution
|
|||
else
|
||||
return ctx->arena->addType(IntersectionType{newParts.take()});
|
||||
}
|
||||
return ty;
|
||||
else if (FFlag::LuauRefineOccursCheckDirectRecursion && ty == needle)
|
||||
return ctx->builtins->unknownType;
|
||||
else
|
||||
return ty;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1285,25 +1286,22 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
|
||||
TypeId targetTy = follow(typeParams.at(0));
|
||||
|
||||
if (FFlag::LuauOccursCheckForRefinement)
|
||||
// If we end up minting a refine type like:
|
||||
//
|
||||
// t1 where t1 = refine<T | t1, Y>
|
||||
//
|
||||
// This can create a degenerate set type such as:
|
||||
//
|
||||
// t1 where t1 = (T | t1) & Y
|
||||
//
|
||||
// Instead, we can clip the recursive part:
|
||||
//
|
||||
// t1 where t1 = refine<T | t1, Y> => refine<T, Y>
|
||||
if (occurs(targetTy, instance))
|
||||
{
|
||||
// If we end up minting a refine type like:
|
||||
//
|
||||
// t1 where t1 = refine<T | t1, Y>
|
||||
//
|
||||
// This can create a degenerate set type such as:
|
||||
//
|
||||
// t1 where t1 = (T | t1) & Y
|
||||
//
|
||||
// Instead, we can clip the recursive part:
|
||||
//
|
||||
// t1 where t1 = refine<T | t1, Y> => refine<T, Y>
|
||||
if (!FFlag::LuauAvoidExcessiveTypeCopying || occurs(targetTy, instance))
|
||||
{
|
||||
RefineTypeScrubber rts{ctx, instance};
|
||||
if (auto result = rts.substitute(targetTy))
|
||||
targetTy = *result;
|
||||
}
|
||||
RefineTypeScrubber rts{ctx, instance};
|
||||
if (auto result = rts.substitute(targetTy))
|
||||
targetTy = *result;
|
||||
}
|
||||
|
||||
std::vector<TypeId> discriminantTypes;
|
||||
|
@ -1363,10 +1361,16 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
if (!frb.found.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}};
|
||||
|
||||
int stepRefineCount = 0;
|
||||
|
||||
// Refine a target type and a discriminant one at a time.
|
||||
// Returns result : TypeId, toBlockOn : vector<TypeId>
|
||||
auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
||||
auto stepRefine = [&stepRefineCount, &ctx](TypeId target, TypeId discriminant) -> std::pair<TypeId, std::vector<TypeId>>
|
||||
{
|
||||
std::optional<RecursionLimiter> rl;
|
||||
if (FFlag::LuauRefineDistributesOverUnions)
|
||||
rl.emplace("BuiltInTypeFunctions::stepRefine", &stepRefineCount, DFInt::LuauStepRefineRecursionLimit);
|
||||
|
||||
std::vector<TypeId> toBlock;
|
||||
// we need a more complex check for blocking on the discriminant in particular
|
||||
FindRefinementBlockers frb;
|
||||
|
@ -1406,11 +1410,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
return {target, {}};
|
||||
}
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
{
|
||||
if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant))
|
||||
return {*ty, {}};
|
||||
}
|
||||
if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant))
|
||||
return {*ty, {}};
|
||||
|
||||
// NOTE: This block causes us to refine too early in some cases.
|
||||
if (auto negation = get<NegationType>(discriminant))
|
||||
|
@ -1470,7 +1471,12 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection);
|
||||
// include the error type if the target type is error-suppressing and the intersection we computed is not
|
||||
if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors())
|
||||
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
resultTy = addUnion(ctx->arena, ctx->builtins, {resultTy, ctx->builtins->errorType});
|
||||
else
|
||||
resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}});
|
||||
}
|
||||
|
||||
return {resultTy, {}};
|
||||
}
|
||||
|
@ -1483,6 +1489,59 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
|
|||
while (!discriminantTypes.empty())
|
||||
{
|
||||
TypeId discriminant = discriminantTypes.back();
|
||||
|
||||
if (FFlag::LuauRefineDistributesOverUnions)
|
||||
{
|
||||
discriminant = follow(discriminant);
|
||||
|
||||
// first, we'll see if simplifying the discriminant alone will solve our problem...
|
||||
if (auto ut = get<UnionType>(discriminant))
|
||||
{
|
||||
TypeId workingType = ctx->builtins->neverType;
|
||||
|
||||
for (auto optionAsDiscriminant : ut->options)
|
||||
{
|
||||
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, workingType, optionAsDiscriminant);
|
||||
|
||||
if (!simplified.blockedTypes.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
|
||||
|
||||
workingType = simplified.result;
|
||||
}
|
||||
|
||||
discriminant = workingType;
|
||||
}
|
||||
|
||||
// if not, we try distributivity: a & (b | c) <=> (a & b) | (a & c)
|
||||
if (auto ut = get<UnionType>(discriminant))
|
||||
{
|
||||
TypeId finalRefined = ctx->builtins->neverType;
|
||||
|
||||
for (auto optionAsDiscriminant : ut->options)
|
||||
{
|
||||
auto [refined, blocked] = stepRefine(target, follow(optionAsDiscriminant));
|
||||
|
||||
if (blocked.empty() && refined == nullptr)
|
||||
return {std::nullopt, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
if (!blocked.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, blocked, {}};
|
||||
|
||||
SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, finalRefined, refined);
|
||||
|
||||
if (!simplified.blockedTypes.empty())
|
||||
return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}};
|
||||
|
||||
finalRefined = simplified.result;
|
||||
}
|
||||
|
||||
target = finalRefined;
|
||||
discriminantTypes.pop_back();
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto [refined, blocked] = stepRefine(target, discriminant);
|
||||
|
||||
if (blocked.empty() && refined == nullptr)
|
||||
|
@ -1672,16 +1731,13 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
|||
if (get<NoRefineType>(ty))
|
||||
continue;
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty))
|
||||
{
|
||||
if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty))
|
||||
{
|
||||
if (get<NeverType>(*simpleResult))
|
||||
unintersectableTypes.insert(follow(ty));
|
||||
else
|
||||
resultTy = *simpleResult;
|
||||
continue;
|
||||
}
|
||||
if (get<NeverType>(*simpleResult))
|
||||
unintersectableTypes.insert(follow(ty));
|
||||
else
|
||||
resultTy = *simpleResult;
|
||||
continue;
|
||||
}
|
||||
|
||||
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty);
|
||||
|
@ -1728,84 +1784,6 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
|
|||
return {resultTy, Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
||||
// computes the keys of `ty` into `result`
|
||||
// `isRaw` parameter indicates whether or not we should follow __index metamethods
|
||||
// returns `false` if `result` should be ignored because the answer is "all strings"
|
||||
bool computeKeysOf_DEPRECATED(TypeId ty, Set<std::string>& result, DenseHashSet<TypeId>& seen, bool isRaw, NotNull<TypeFunctionContext> ctx)
|
||||
{
|
||||
// if the type is the top table type, the answer is just "all strings"
|
||||
if (get<PrimitiveType>(ty))
|
||||
return false;
|
||||
|
||||
// if we've already seen this type, we can do nothing
|
||||
if (seen.contains(ty))
|
||||
return true;
|
||||
seen.insert(ty);
|
||||
|
||||
// if we have a particular table type, we can insert the keys
|
||||
if (auto tableTy = get<TableType>(ty))
|
||||
{
|
||||
if (tableTy->indexer)
|
||||
{
|
||||
// if we have a string indexer, the answer is, again, "all strings"
|
||||
if (isString(tableTy->indexer->indexType))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto [key, _] : tableTy->props)
|
||||
result.insert(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, we have a metatable to deal with
|
||||
if (auto metatableTy = get<MetatableType>(ty))
|
||||
{
|
||||
bool res = true;
|
||||
|
||||
if (!isRaw)
|
||||
{
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
|
||||
if (mmType)
|
||||
res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx);
|
||||
}
|
||||
|
||||
res = res && computeKeysOf_DEPRECATED(metatableTy->table, result, seen, isRaw, ctx);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
if (auto classTy = get<ExternType>(ty))
|
||||
{
|
||||
for (auto [key, _] : classTy->props) // NOLINT(performance-for-range-copy)
|
||||
result.insert(key);
|
||||
|
||||
bool res = true;
|
||||
if (classTy->metatable && !isRaw)
|
||||
{
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{});
|
||||
if (mmType)
|
||||
res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx);
|
||||
}
|
||||
|
||||
if (classTy->parent)
|
||||
res = res && computeKeysOf_DEPRECATED(follow(*classTy->parent), result, seen, isRaw, ctx);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// this should not be reachable since the type should be a valid tables or extern types part from normalization.
|
||||
LUAU_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
@ -1923,201 +1901,102 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
|
|||
normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars())
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||
|
||||
if (FFlag::LuauEmptyStringInKeyOf)
|
||||
// We're going to collect the keys in here, and we use optional strings
|
||||
// so that we can differentiate between the empty string and _no_ string.
|
||||
Set<std::optional<std::string>> keys{std::nullopt};
|
||||
|
||||
// computing the keys for extern types
|
||||
if (normTy->hasExternTypes())
|
||||
{
|
||||
// We're going to collect the keys in here, and we use optional strings
|
||||
// so that we can differentiate between the empty string and _no_ string.
|
||||
Set<std::optional<std::string>> keys{std::nullopt};
|
||||
LUAU_ASSERT(!normTy->hasTables());
|
||||
|
||||
// computing the keys for extern types
|
||||
if (normTy->hasExternTypes())
|
||||
// seen set for key computation for extern types
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
|
||||
auto externTypeIter = normTy->externTypes.ordering.begin();
|
||||
auto externTypeIterEnd = normTy->externTypes.ordering.end();
|
||||
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
|
||||
|
||||
// collect all the properties from the first class type
|
||||
if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
|
||||
|
||||
// we need to look at each class to remove any keys that are not common amongst them all
|
||||
while (++externTypeIter != externTypeIterEnd)
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasTables());
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
// seen set for key computation for extern types
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
Set<std::optional<std::string>> localKeys{std::nullopt};
|
||||
|
||||
auto externTypeIter = normTy->externTypes.ordering.begin();
|
||||
auto externTypeIterEnd = normTy->externTypes.ordering.end();
|
||||
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
|
||||
// we can skip to the next class if this one is a top type
|
||||
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
// collect all the properties from the first class type
|
||||
if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
|
||||
|
||||
// we need to look at each class to remove any keys that are not common amongst them all
|
||||
while (++externTypeIter != externTypeIterEnd)
|
||||
for (auto& key : keys)
|
||||
{
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
Set<std::optional<std::string>> localKeys{std::nullopt};
|
||||
|
||||
// we can skip to the next class if this one is a top type
|
||||
if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
for (auto& key : keys)
|
||||
{
|
||||
// remove any keys that are not present in each class
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
// remove any keys that are not present in each class
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
// computing the keys for tables
|
||||
if (normTy->hasTables())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasExternTypes());
|
||||
|
||||
// seen set for key computation for tables
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
|
||||
auto tablesIter = normTy->tables.begin();
|
||||
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
|
||||
|
||||
// collect all the properties from the first table type
|
||||
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type!
|
||||
|
||||
// we need to look at each tables to remove any keys that are not common amongst them all
|
||||
while (++tablesIter != normTy->tables.end())
|
||||
{
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
Set<std::optional<std::string>> localKeys{std::nullopt};
|
||||
|
||||
// we can skip to the next table if this one is the top table type
|
||||
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
for (auto& key : keys)
|
||||
{
|
||||
// remove any keys that are not present in each table
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the set of keys is empty, `keyof<T>` is `never`
|
||||
if (keys.empty())
|
||||
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// everything is validated, we need only construct our big union of singletons now!
|
||||
std::vector<TypeId> singletons;
|
||||
singletons.reserve(keys.size());
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (key)
|
||||
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}}));
|
||||
}
|
||||
|
||||
// If there's only one entry, we don't need a UnionType.
|
||||
// We can take straight take it from the first entry
|
||||
// because it was added into the type arena already.
|
||||
if (singletons.size() == 1)
|
||||
return {singletons.front(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
else
|
||||
|
||||
// computing the keys for tables
|
||||
if (normTy->hasTables())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasExternTypes());
|
||||
|
||||
// we're going to collect the keys in here
|
||||
Set<std::string> keys{{}};
|
||||
// seen set for key computation for tables
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
|
||||
// computing the keys for extern types
|
||||
if (normTy->hasExternTypes())
|
||||
auto tablesIter = normTy->tables.begin();
|
||||
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
|
||||
|
||||
// collect all the properties from the first table type
|
||||
if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type!
|
||||
|
||||
// we need to look at each tables to remove any keys that are not common amongst them all
|
||||
while (++tablesIter != normTy->tables.end())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasTables());
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
// seen set for key computation for extern types
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
Set<std::optional<std::string>> localKeys{std::nullopt};
|
||||
|
||||
auto externTypeIter = normTy->externTypes.ordering.begin();
|
||||
auto externTypeIterEnd = normTy->externTypes.ordering.end();
|
||||
LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier
|
||||
// we can skip to the next table if this one is the top table type
|
||||
if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
// collect all the properties from the first class type
|
||||
if (!computeKeysOf_DEPRECATED(*externTypeIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type!
|
||||
|
||||
// we need to look at each class to remove any keys that are not common amongst them all
|
||||
while (++externTypeIter != externTypeIterEnd)
|
||||
for (auto& key : keys)
|
||||
{
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
Set<std::string> localKeys{{}};
|
||||
|
||||
// we can skip to the next class if this one is a top type
|
||||
if (!computeKeysOf_DEPRECATED(*externTypeIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
for (auto& key : keys)
|
||||
{
|
||||
// remove any keys that are not present in each class
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
// remove any keys that are not present in each table
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
// computing the keys for tables
|
||||
if (normTy->hasTables())
|
||||
{
|
||||
LUAU_ASSERT(!normTy->hasExternTypes());
|
||||
|
||||
// seen set for key computation for tables
|
||||
DenseHashSet<TypeId> seen{{}};
|
||||
|
||||
auto tablesIter = normTy->tables.begin();
|
||||
LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier
|
||||
|
||||
// collect all the properties from the first table type
|
||||
if (!computeKeysOf_DEPRECATED(*tablesIter, keys, seen, isRaw, ctx))
|
||||
return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type!
|
||||
|
||||
// we need to look at each tables to remove any keys that are not common amongst them all
|
||||
while (++tablesIter != normTy->tables.end())
|
||||
{
|
||||
seen.clear(); // we'll reuse the same seen set
|
||||
|
||||
Set<std::string> localKeys{{}};
|
||||
|
||||
// we can skip to the next table if this one is the top table type
|
||||
if (!computeKeysOf_DEPRECATED(*tablesIter, localKeys, seen, isRaw, ctx))
|
||||
continue;
|
||||
|
||||
for (auto& key : keys)
|
||||
{
|
||||
// remove any keys that are not present in each table
|
||||
if (!localKeys.contains(key))
|
||||
keys.erase(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the set of keys is empty, `keyof<T>` is `never`
|
||||
if (keys.empty())
|
||||
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// everything is validated, we need only construct our big union of singletons now!
|
||||
std::vector<TypeId> singletons;
|
||||
singletons.reserve(keys.size());
|
||||
|
||||
for (const std::string& key : keys)
|
||||
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
|
||||
|
||||
// If there's only one entry, we don't need a UnionType.
|
||||
// We can take straight take it from the first entry
|
||||
// because it was added into the type arena already.
|
||||
if (singletons.size() == 1)
|
||||
return {singletons.front(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
||||
// if the set of keys is empty, `keyof<T>` is `never`
|
||||
if (keys.empty())
|
||||
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
|
||||
|
||||
// everything is validated, we need only construct our big union of singletons now!
|
||||
std::vector<TypeId> singletons;
|
||||
singletons.reserve(keys.size());
|
||||
|
||||
for (const auto& key : keys)
|
||||
{
|
||||
if (key)
|
||||
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}}));
|
||||
}
|
||||
|
||||
// If there's only one entry, we don't need a UnionType.
|
||||
// We can take straight take it from the first entry
|
||||
// because it was added into the type arena already.
|
||||
if (singletons.size() == 1)
|
||||
return {singletons.front(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}};
|
||||
}
|
||||
|
||||
TypeFunctionReductionResult<TypeId> keyofTypeFunction(
|
||||
|
@ -2618,7 +2497,7 @@ static TypeFunctionReductionResult<TypeId> getmetatableHelper(TypeId targetTy, c
|
|||
erroneous = false;
|
||||
}
|
||||
|
||||
if (FFlag::LuauUpdateGetMetatableTypeSignature && get<AnyType>(targetTy))
|
||||
if (get<AnyType>(targetTy))
|
||||
{
|
||||
// getmetatable<any> ~ any
|
||||
result = targetTy;
|
||||
|
@ -2695,7 +2574,7 @@ TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
|
|||
if (!result.result)
|
||||
{
|
||||
// Don't immediately error if part is unknown
|
||||
if (FFlag::LuauUpdateGetMetatableTypeSignature && get<UnknownType>(follow(part)))
|
||||
if (get<UnknownType>(follow(part)))
|
||||
{
|
||||
erroredWithUnknown = true;
|
||||
continue;
|
||||
|
@ -2708,10 +2587,10 @@ TypeFunctionReductionResult<TypeId> getmetatableTypeFunction(
|
|||
}
|
||||
|
||||
// If all parts are unknown, return erroneous reduction
|
||||
if (FFlag::LuauUpdateGetMetatableTypeSignature && erroredWithUnknown && parts.empty())
|
||||
if (erroredWithUnknown && parts.empty())
|
||||
return {std::nullopt, Reduction::Erroneous, {}, {}};
|
||||
|
||||
if (FFlag::LuauUpdateGetMetatableTypeSignature && parts.size() == 1)
|
||||
if (parts.size() == 1)
|
||||
return {parts.front(), Reduction::MaybeOk, {}, {}};
|
||||
|
||||
return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}};
|
||||
|
|
|
@ -37,15 +37,17 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson)
|
|||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -232,6 +234,7 @@ ConstraintGenerator::ConstraintGenerator(
|
|||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauTrackFreeInteriorTypePacks);
|
||||
LUAU_ASSERT(FFlag::LuauResetConditionalContextProperly);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +296,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
moduleFnTy,
|
||||
/*interiorTypes*/ std::vector<TypeId>{},
|
||||
/*hasDeprecatedAttribute*/ false,
|
||||
/*deprecatedInfo*/{},
|
||||
/*noGenerics*/ true
|
||||
}
|
||||
);
|
||||
|
@ -312,7 +316,10 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
this,
|
||||
[genConstraint](const ConstraintPtr& c)
|
||||
{
|
||||
genConstraint->dependencies.push_back(NotNull{c.get()});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
genConstraint->dependencies.emplace_back(c.get());
|
||||
else
|
||||
genConstraint->dependencies.push_back(NotNull{c.get()});
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -342,11 +349,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
|||
asMutable(ty)->ty.emplace<BoundType>(domainTy);
|
||||
}
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
for (TypeId ty : unionsToSimplify)
|
||||
addConstraint(scope, block->location, SimplifyConstraint{ty});
|
||||
}
|
||||
for (TypeId ty : unionsToSimplify)
|
||||
addConstraint(scope, block->location, SimplifyConstraint{ty});
|
||||
}
|
||||
|
||||
void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block)
|
||||
|
@ -442,7 +446,10 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent)
|
|||
scope->returnType = parent->returnType;
|
||||
scope->varargPack = parent->varargPack;
|
||||
|
||||
parent->children.push_back(NotNull{scope.get()});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
parent->children.emplace_back(scope.get());
|
||||
else
|
||||
parent->children.push_back(NotNull{scope.get()});
|
||||
module->astScopes[node] = scope.get();
|
||||
|
||||
return scope;
|
||||
|
@ -608,18 +615,7 @@ void ConstraintGenerator::computeRefinement(
|
|||
|
||||
TypeId nextDiscriminantTy = arena->addType(TableType{});
|
||||
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
{
|
||||
table->props[*key->propName] = Property::readonly(discriminantTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we fully support read-write properties (i.e. when we allow properties with
|
||||
// completely disparate read and write types), then the following property can be
|
||||
// set to read-only since refinements only tell us about what we read. This cannot
|
||||
// be allowed yet though because it causes read and write types to diverge.
|
||||
table->props[*key->propName] = Property::rw(discriminantTy);
|
||||
}
|
||||
table->props[*key->propName] = Property::readonly(discriminantTy);
|
||||
table->scope = scope.get();
|
||||
table->state = TableState::Sealed;
|
||||
|
||||
|
@ -1133,7 +1129,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
|||
hasAnnotation = true;
|
||||
TypeId annotationTy = resolveType(scope, local->annotation, /* inTypeArguments */ false);
|
||||
annotatedTypes.push_back(annotationTy);
|
||||
expectedTypes.push_back(annotationTy);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
expectedTypes.emplace_back(annotationTy);
|
||||
else
|
||||
expectedTypes.push_back(annotationTy);
|
||||
|
||||
scope->bindings[local] = Binding{annotationTy, location};
|
||||
}
|
||||
|
@ -1143,7 +1142,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
|||
// local has no annotation at, assume the most conservative thing.
|
||||
annotatedTypes.push_back(builtinTypes->unknownType);
|
||||
|
||||
expectedTypes.push_back(std::nullopt);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
expectedTypes.emplace_back(std::nullopt);
|
||||
else
|
||||
expectedTypes.push_back(std::nullopt);
|
||||
scope->bindings[local] = Binding{builtinTypes->unknownType, location};
|
||||
|
||||
inferredBindings[local] = {scope.get(), location, {assignee}};
|
||||
|
@ -1407,7 +1409,19 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx
|
|||
{
|
||||
if (GeneralizationConstraint* genConstraint = c.get_if<GeneralizationConstraint>())
|
||||
{
|
||||
genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttribute = func->getAttribute(AstAttr::Type::Deprecated);
|
||||
genConstraint->hasDeprecatedAttribute = deprecatedAttribute != nullptr;
|
||||
if (deprecatedAttribute)
|
||||
{
|
||||
genConstraint->deprecatedInfo = deprecatedAttribute->deprecatedInfo();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1415,7 +1429,19 @@ static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFu
|
|||
{
|
||||
FunctionType* fty = getMutable<FunctionType>(signature);
|
||||
LUAU_ASSERT(fty);
|
||||
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated);
|
||||
fty->isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
fty->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function)
|
||||
|
@ -1470,12 +1496,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti
|
|||
this,
|
||||
[&c, &previous](const ConstraintPtr& constraint)
|
||||
{
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
c->dependencies.emplace_back(constraint.get());
|
||||
else
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
constraint->dependencies.emplace_back(previous);
|
||||
else
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
}
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
|
@ -1604,12 +1638,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
|||
this,
|
||||
[&c, &previous](const ConstraintPtr& constraint)
|
||||
{
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
c->dependencies.emplace_back(constraint.get());
|
||||
else
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
constraint->dependencies.emplace_back(previous);
|
||||
else
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
}
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
|
@ -1667,7 +1709,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatReturn* ret
|
|||
// conforms to that.
|
||||
std::vector<std::optional<TypeId>> expectedTypes;
|
||||
for (TypeId ty : scope->returnType)
|
||||
expectedTypes.push_back(ty);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
expectedTypes.emplace_back(ty);
|
||||
else
|
||||
expectedTypes.push_back(ty);
|
||||
|
||||
TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes).tp;
|
||||
addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType, /*returns*/ true});
|
||||
|
@ -1868,7 +1913,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
|
|||
FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt);
|
||||
|
||||
// Place this function as a child of the non-type function scope
|
||||
scope->children.push_back(NotNull{sig.signatureScope.get()});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
scope->children.emplace_back(sig.signatureScope.get());
|
||||
else
|
||||
scope->children.push_back(NotNull{sig.signatureScope.get()});
|
||||
|
||||
if (FFlag::LuauTrackFreeInteriorTypePacks)
|
||||
interiorFreeTypes.emplace_back();
|
||||
|
@ -1914,7 +1962,12 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
|
|||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
constraint->dependencies.emplace_back(previous);
|
||||
else
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
}
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
|
@ -2171,11 +2224,26 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc
|
|||
|
||||
FunctionType* ftv = getMutable<FunctionType>(fnType);
|
||||
ftv->isCheckedFunction = global->isCheckedFunction();
|
||||
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = global->getAttribute(AstAttr::Type::Deprecated);
|
||||
ftv->isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
ftv->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
|
||||
ftv->argNames.reserve(global->paramNames.size);
|
||||
for (const auto& el : global->paramNames)
|
||||
ftv->argNames.push_back(FunctionArgument{el.first.value, el.second});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv->argNames.emplace_back(FunctionArgument{el.first.value, el.second});
|
||||
else
|
||||
ftv->argNames.push_back(FunctionArgument{el.first.value, el.second});
|
||||
|
||||
Name fnName(global->name.value);
|
||||
|
||||
|
@ -2288,8 +2356,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
{
|
||||
TypeId discriminantTy = arena->addType(BlockedType{});
|
||||
returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy));
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
discriminantTypes.emplace_back(discriminantTy);
|
||||
else
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
}
|
||||
else if (FFlag::LuauEmplaceNotPushBack)
|
||||
discriminantTypes.emplace_back(std::nullopt);
|
||||
else
|
||||
discriminantTypes.push_back(std::nullopt);
|
||||
}
|
||||
|
@ -2302,8 +2375,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
{
|
||||
TypeId discriminantTy = arena->addType(BlockedType{});
|
||||
returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy));
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
discriminantTypes.emplace_back(discriminantTy);
|
||||
else
|
||||
discriminantTypes.push_back(discriminantTy);
|
||||
}
|
||||
else if (FFlag::LuauEmplaceNotPushBack)
|
||||
discriminantTypes.emplace_back(std::nullopt);
|
||||
else
|
||||
discriminantTypes.push_back(std::nullopt);
|
||||
}
|
||||
|
@ -2311,7 +2389,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
|
|||
Checkpoint funcBeginCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId fnType = nullptr;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
{
|
||||
InConditionalContext icc2{&typeContext, TypeContext::Default};
|
||||
fnType = check(scope, call->func).ty;
|
||||
|
@ -2828,7 +2906,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* func, std::optional<TypeId> expectedType, bool generalize)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
|
@ -2879,7 +2957,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
|||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
constraint->dependencies.emplace_back(previous);
|
||||
else
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
}
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
|
@ -2899,7 +2982,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4 && unary->op != AstExprUnary::Op::Not)
|
||||
if (FFlag::LuauResetConditionalContextProperly && unary->op != AstExprUnary::Op::Not)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
auto [operandType, refinement] = check(scope, unary->expr);
|
||||
|
@ -3064,7 +3147,7 @@ Inference ConstraintGenerator::checkAstExprBinary(
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
RefinementId refinement = [&]()
|
||||
|
@ -3097,7 +3180,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprInterpString* interpString)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
for (AstExpr* expr : interpString->expressions)
|
||||
|
@ -3115,7 +3198,7 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
|
|||
)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
{
|
||||
if (op != AstExprBinary::And && op != AstExprBinary::Or && op != AstExprBinary::CompareEq && op != AstExprBinary::CompareNe)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
@ -3362,7 +3445,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexExpr* e
|
|||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
TypeId ty = arena->addType(TableType{});
|
||||
|
@ -3430,44 +3513,30 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
|||
{
|
||||
LUAU_ASSERT(!indexValueLowerBound.empty());
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
TypeId indexKey = nullptr;
|
||||
TypeId indexValue = nullptr;
|
||||
|
||||
if (indexKeyLowerBound.size() == 1)
|
||||
{
|
||||
TypeId indexKey = nullptr;
|
||||
TypeId indexValue = nullptr;
|
||||
|
||||
if (indexKeyLowerBound.size() == 1)
|
||||
{
|
||||
indexKey = *indexKeyLowerBound.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
|
||||
unionsToSimplify.push_back(indexKey);
|
||||
}
|
||||
|
||||
if (indexValueLowerBound.size() == 1)
|
||||
{
|
||||
indexValue = *indexValueLowerBound.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
|
||||
unionsToSimplify.push_back(indexValue);
|
||||
}
|
||||
|
||||
ttv->indexer = TableIndexer{indexKey, indexValue};
|
||||
indexKey = *indexKeyLowerBound.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId indexKey = indexKeyLowerBound.size() == 1
|
||||
? *indexKeyLowerBound.begin()
|
||||
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
|
||||
|
||||
TypeId indexValue = indexValueLowerBound.size() == 1
|
||||
? *indexValueLowerBound.begin()
|
||||
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
|
||||
ttv->indexer = TableIndexer{indexKey, indexValue};
|
||||
indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
|
||||
unionsToSimplify.push_back(indexKey);
|
||||
}
|
||||
|
||||
if (indexValueLowerBound.size() == 1)
|
||||
{
|
||||
indexValue = *indexValueLowerBound.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
|
||||
unionsToSimplify.push_back(indexValue);
|
||||
}
|
||||
|
||||
ttv->indexer = TableIndexer{indexKey, indexValue};
|
||||
}
|
||||
|
||||
if (FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit))
|
||||
|
@ -3906,7 +3975,20 @@ TypeId ConstraintGenerator::resolveFunctionType(
|
|||
// how to quantify/instantiate it.
|
||||
FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes};
|
||||
ftv.isCheckedFunction = fn->isCheckedFunction();
|
||||
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = fn->getAttribute(AstAttr::Type::Deprecated);
|
||||
ftv.isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
ftv.deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated);
|
||||
}
|
||||
|
||||
|
||||
// This replicates the behavior of the appropriate FunctionType
|
||||
// constructors.
|
||||
|
@ -3919,12 +4001,15 @@ TypeId ConstraintGenerator::resolveFunctionType(
|
|||
if (el)
|
||||
{
|
||||
const auto& [name, location] = *el;
|
||||
ftv.argNames.push_back(FunctionArgument{name.value, location});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv.argNames.emplace_back(FunctionArgument{name.value, location});
|
||||
else
|
||||
ftv.argNames.push_back(FunctionArgument{name.value, location});
|
||||
}
|
||||
else if (FFlag::LuauEmplaceNotPushBack)
|
||||
ftv.argNames.emplace_back(std::nullopt);
|
||||
else
|
||||
{
|
||||
ftv.argNames.push_back(std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
return arena->addType(std::move(ftv));
|
||||
|
@ -4173,7 +4258,10 @@ Inference ConstraintGenerator::flattenPack(const ScopePtr& scope, Location locat
|
|||
|
||||
void ConstraintGenerator::reportError(Location location, TypeErrorData err)
|
||||
{
|
||||
errors.push_back(TypeError{location, module->name, std::move(err)});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(location, module->name, std::move(err));
|
||||
else
|
||||
errors.push_back(TypeError{location, module->name, std::move(err)});
|
||||
|
||||
if (logger)
|
||||
logger->captureGenerationError(errors.back());
|
||||
|
@ -4181,7 +4269,10 @@ void ConstraintGenerator::reportError(Location location, TypeErrorData err)
|
|||
|
||||
void ConstraintGenerator::reportCodeTooComplex(Location location)
|
||||
{
|
||||
errors.push_back(TypeError{location, module->name, CodeTooComplex{}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(location, module->name, CodeTooComplex{});
|
||||
else
|
||||
errors.push_back(TypeError{location, module->name, CodeTooComplex{}});
|
||||
|
||||
if (logger)
|
||||
logger->captureGenerationError(errors.back());
|
||||
|
@ -4194,23 +4285,30 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
|
|||
if (get<NeverType>(follow(rhs)))
|
||||
return lhs;
|
||||
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
TypeId result = simplifyUnion(scope, location, lhs, rhs);
|
||||
if (is<UnionType>(follow(result)))
|
||||
unionsToSimplify.push_back(result);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location);
|
||||
return resultType;
|
||||
}
|
||||
TypeId result = simplifyUnion(scope, location, lhs, rhs);
|
||||
if (is<UnionType>(follow(result)))
|
||||
unionsToSimplify.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::makeUnion(std::vector<TypeId> options)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine2);
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
UnionBuilder ub{arena, builtinTypes};
|
||||
ub.reserve(options.size());
|
||||
|
||||
for (auto option : options)
|
||||
ub.add(option);
|
||||
|
||||
TypeId unionTy = ub.build();
|
||||
|
||||
if (is<UnionType>(unionTy))
|
||||
unionsToSimplify.push_back(unionTy);
|
||||
|
||||
return unionTy;
|
||||
}
|
||||
|
||||
TypeId result = arena->addType(UnionType{std::move(options)});
|
||||
unionsToSimplify.push_back(result);
|
||||
return result;
|
||||
|
@ -4423,16 +4521,8 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
|
|||
scope->bindings[symbol] = Binding{tys.front(), location};
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
{
|
||||
TypeId ty = makeUnion(std::move(tys));
|
||||
scope->bindings[symbol] = Binding{ty, location};
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location);
|
||||
scope->bindings[symbol] = Binding{ty, location};
|
||||
}
|
||||
TypeId ty = makeUnion(std::move(tys));
|
||||
scope->bindings[symbol] = Binding{ty, location};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4455,7 +4545,12 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
|
|||
auto assignOption = [this, &expectedTypes](size_t index, TypeId ty)
|
||||
{
|
||||
if (index == expectedTypes.size())
|
||||
expectedTypes.push_back(ty);
|
||||
{
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
expectedTypes.emplace_back(ty);
|
||||
else
|
||||
expectedTypes.push_back(ty);
|
||||
}
|
||||
else if (ty)
|
||||
{
|
||||
auto& el = expectedTypes[index];
|
||||
|
@ -4470,12 +4565,7 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
|
|||
else if (result.size() == 1)
|
||||
el = result[0];
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSimplifyOutOfLine2)
|
||||
el = makeUnion(std::move(result));
|
||||
else
|
||||
el = module->internalTypes.addType(UnionType{std::move(result)});
|
||||
}
|
||||
el = makeUnion(std::move(result));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSolverConstraintLimit, 1000)
|
||||
|
@ -38,11 +39,16 @@ LUAU_FASTFLAG(LuauEagerGeneralization4)
|
|||
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
|
||||
LUAU_FASTFLAG(LuauLimitUnification)
|
||||
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash)
|
||||
LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDontDynamicallyCreateRedundantSubtypeConstraints)
|
||||
LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -307,7 +313,7 @@ struct InstantiationQueuer : TypeOnceVisitor
|
|||
Location location;
|
||||
|
||||
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
|
||||
: TypeOnceVisitor("InstantiationQueuer")
|
||||
: TypeOnceVisitor("InstantiationQueuer", FFlag::LuauReduceSetTypeStackPressure)
|
||||
, solver(solver)
|
||||
, scope(scope)
|
||||
, location(location)
|
||||
|
@ -332,6 +338,42 @@ struct InstantiationQueuer : TypeOnceVisitor
|
|||
}
|
||||
};
|
||||
|
||||
struct InfiniteTypeFinder : TypeOnceVisitor
|
||||
{
|
||||
NotNull<ConstraintSolver> solver;
|
||||
const InstantiationSignature& signature;
|
||||
NotNull<Scope> scope;
|
||||
bool foundInfiniteType = false;
|
||||
|
||||
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
|
||||
: TypeOnceVisitor("InfiniteTypeFinder")
|
||||
, solver(solver)
|
||||
, signature(signature)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType& petv) override
|
||||
{
|
||||
const std::optional<TypeFun> tf =
|
||||
(petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value);
|
||||
|
||||
if (!tf.has_value())
|
||||
return true;
|
||||
|
||||
auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->builtinTypes, *tf, petv.typeArguments, petv.packArguments);
|
||||
|
||||
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
|
||||
{
|
||||
foundInfiniteType = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
ConstraintSolver::ConstraintSolver(
|
||||
NotNull<Normalizer> normalizer,
|
||||
NotNull<Simplifier> simplifier,
|
||||
|
@ -911,7 +953,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
if (FunctionType* fty = getMutable<FunctionType>(follow(generalizedType)))
|
||||
{
|
||||
if (c.hasDeprecatedAttribute)
|
||||
{
|
||||
fty->isDeprecatedFunction = true;
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
fty->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(c.deprecatedInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1115,6 +1163,30 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
|
|||
if (target->persistent || target->owningArena != arena)
|
||||
return true;
|
||||
|
||||
if (FFlag::LuauNameConstraintRestrictRecursiveTypes)
|
||||
{
|
||||
if (std::optional<TypeFun> tf = constraint->scope->lookupType(c.name))
|
||||
{
|
||||
// We check to see if this type alias violates the recursion restriction
|
||||
InstantiationSignature signature{
|
||||
*tf,
|
||||
c.typeParameters,
|
||||
c.typePackParameters,
|
||||
};
|
||||
|
||||
InfiniteTypeFinder itf{this, signature, constraint->scope};
|
||||
itf.traverse(target);
|
||||
|
||||
if (itf.foundInfiniteType)
|
||||
{
|
||||
constraint->scope->invalidTypeAliasNames.insert(c.name);
|
||||
shiftReferences(target, builtinTypes->errorType);
|
||||
emplaceType<BoundType>(asMutable(target), builtinTypes->errorType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TableType* ttv = getMutable<TableType>(target))
|
||||
{
|
||||
if (c.synthetic && !ttv->name)
|
||||
|
@ -1136,41 +1208,6 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull<const Constr
|
|||
return true;
|
||||
}
|
||||
|
||||
struct InfiniteTypeFinder : TypeOnceVisitor
|
||||
{
|
||||
ConstraintSolver* solver;
|
||||
const InstantiationSignature& signature;
|
||||
NotNull<Scope> scope;
|
||||
bool foundInfiniteType = false;
|
||||
|
||||
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
|
||||
: TypeOnceVisitor("InfiniteTypeFinder")
|
||||
, solver(solver)
|
||||
, signature(signature)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const PendingExpansionType& petv) override
|
||||
{
|
||||
std::optional<TypeFun> tf =
|
||||
(petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value);
|
||||
|
||||
if (!tf.has_value())
|
||||
return true;
|
||||
|
||||
auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->builtinTypes, *tf, petv.typeArguments, petv.packArguments);
|
||||
|
||||
if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments))
|
||||
{
|
||||
foundInfiniteType = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
const PendingExpansionType* petv = get<PendingExpansionType>(follow(c.target));
|
||||
|
@ -1553,7 +1590,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||
// TODO: This should probably use ConstraintSolver::unify
|
||||
const UnifyResult unifyResult = u2.unify(overloadToUse, inferredTy);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
|
@ -1584,10 +1622,27 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
upperBoundContributors[expanded].emplace_back(constraint->location, addition);
|
||||
}
|
||||
|
||||
if (occursCheckPassed && c.callSite)
|
||||
if (UnifyResult::Ok == unifyResult && c.callSite)
|
||||
(*c.astOverloadResolvedTypes)[c.callSite] = inferredTy;
|
||||
else if (!occursCheckPassed)
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
else if (UnifyResult::Ok != unifyResult)
|
||||
{
|
||||
if (FFlag::LuauLimitUnification)
|
||||
{
|
||||
switch (unifyResult)
|
||||
{
|
||||
case UnifyResult::Ok:
|
||||
break;
|
||||
case UnifyResult::TooComplex:
|
||||
reportError(UnificationTooComplex{}, constraint->location);
|
||||
break;
|
||||
case UnifyResult::OccursCheckFailed:
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
}
|
||||
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(overloadToUse);
|
||||
|
@ -1834,38 +1889,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
|||
else if (expr->is<AstExprConstantBool>() || expr->is<AstExprConstantString>() || expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantNil>() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>()))
|
||||
{
|
||||
if (FFlag::LuauAvoidExcessiveTypeCopying)
|
||||
{
|
||||
if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
|
||||
{
|
||||
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
|
||||
if (auto res = replacer.substitute(expectedArgTy))
|
||||
{
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
expectedArgTy = *res;
|
||||
}
|
||||
}
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
else
|
||||
if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
|
||||
{
|
||||
ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}};
|
||||
if (auto res = replacer.substitute(expectedArgTy))
|
||||
{
|
||||
if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
{
|
||||
// If we do this replacement and there are type
|
||||
// functions in the final type, then we need to
|
||||
// ensure those get reduced.
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
}
|
||||
u2.unify(actualArgTy, *res);
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(*res);
|
||||
expectedArgTy = *res;
|
||||
}
|
||||
else
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
u2.unify(actualArgTy, expectedArgTy);
|
||||
}
|
||||
else if (!FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is<AstExprTable>() &&
|
||||
!ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks}))
|
||||
|
@ -2446,7 +2480,10 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const
|
|||
{
|
||||
unify(constraint, indexType, lhsTable->indexer->indexType);
|
||||
unify(constraint, rhsType, lhsTable->indexer->indexResultType);
|
||||
bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}}));
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
bind(constraint, c.propType, addUnion(arena, builtinTypes, {lhsTable->indexer->indexResultType, builtinTypes->nilType}));
|
||||
else
|
||||
bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}}));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3283,13 +3320,14 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
{
|
||||
const TypeId upperBound = follow(ft->upperBound);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauExtendSealedTableUpperBounds)
|
||||
{
|
||||
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
|
||||
{
|
||||
TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
|
||||
// If the upper bound is a table that already has the property, we don't need to extend its bounds.
|
||||
if (res.propType || get<PrimitiveType>(upperBound))
|
||||
// Here, res.propType is empty if res is a sealed table or a primitive that lacks the property.
|
||||
// When this happens, we still want to add to the upper bound of the type.
|
||||
if (res.propType)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
@ -3299,8 +3337,6 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
|
|||
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
|
||||
}
|
||||
|
||||
// TODO: The upper bound could be an intersection that contains suitable tables or extern types.
|
||||
|
||||
NotNull<Scope> scope{ft->scope};
|
||||
|
||||
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope});
|
||||
|
@ -3409,7 +3445,7 @@ bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TI
|
|||
{
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFunctions};
|
||||
|
||||
const bool ok = u2.unify(subTy, superTy);
|
||||
const UnifyResult unifyResult = u2.unify(subTy, superTy);
|
||||
|
||||
for (ConstraintV& c : u2.incompleteSubtypes)
|
||||
{
|
||||
|
@ -3417,7 +3453,7 @@ bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TI
|
|||
inheritBlocks(constraint, addition);
|
||||
}
|
||||
|
||||
if (ok)
|
||||
if (UnifyResult::Ok == unifyResult)
|
||||
{
|
||||
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||
{
|
||||
|
@ -3427,7 +3463,22 @@ bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TI
|
|||
}
|
||||
else
|
||||
{
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
if (FFlag::LuauLimitUnification)
|
||||
{
|
||||
switch (unifyResult)
|
||||
{
|
||||
case Luau::UnifyResult::Ok:
|
||||
break;
|
||||
case Luau::UnifyResult::OccursCheckFailed:
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
break;
|
||||
case Luau::UnifyResult::TooComplex:
|
||||
reportError(UnificationTooComplex{}, constraint->location);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements)
|
||||
LUAU_FASTFLAGVARIABLE(LuauDfgForwardNilFromAndOr)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1036,23 +1035,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
|
|||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
|
||||
{
|
||||
if (FFlag::LuauDfgForwardNilFromAndOr)
|
||||
{
|
||||
auto left = visitExpr(b->left);
|
||||
auto right = visitExpr(b->right);
|
||||
// I think there's some subtlety here. There are probably cases where
|
||||
// X or Y / X and Y can _never_ "be subscripted."
|
||||
auto subscripted = (b->op == AstExprBinary::And || b->op == AstExprBinary::Or) &&
|
||||
(containsSubscriptedDefinition(left.def) || containsSubscriptedDefinition(right.def));
|
||||
return {defArena->freshCell(Symbol{}, b->location, subscripted), nullptr};
|
||||
}
|
||||
else
|
||||
{
|
||||
visitExpr(b->left);
|
||||
visitExpr(b->right);
|
||||
|
||||
return {defArena->freshCell(Symbol{}, b->location), nullptr};
|
||||
}
|
||||
auto left = visitExpr(b->left);
|
||||
auto right = visitExpr(b->right);
|
||||
// I think there's some subtlety here. There are probably cases where
|
||||
// X or Y / X and Y can _never_ "be subscripted."
|
||||
auto subscripted = (b->op == AstExprBinary::And || b->op == AstExprBinary::Or) &&
|
||||
(containsSubscriptedDefinition(left.def) || containsSubscriptedDefinition(right.def));
|
||||
return {defArena->freshCell(Symbol{}, b->location, subscripted), nullptr};
|
||||
}
|
||||
|
||||
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include "Luau/TypeFunction.h"
|
||||
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
|
@ -890,6 +889,11 @@ struct ErrorConverter
|
|||
{
|
||||
return "None of the overloads for function that accept " + std::to_string(e.attemptedArgCount) + " arguments are compatible.";
|
||||
}
|
||||
|
||||
std::string operator()(const RecursiveRestraintViolation& e) const
|
||||
{
|
||||
return "Recursive type being used with different parameters.";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
|
@ -1516,6 +1520,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
|||
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>)
|
||||
{
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
|
|
@ -16,12 +16,10 @@
|
|||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -29,7 +27,6 @@
|
|||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
|
@ -49,6 +46,7 @@ LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver)
|
|||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete)
|
||||
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -127,14 +125,24 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName)
|
|||
{
|
||||
for (auto& [name, prop] : ttv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
std::string n;
|
||||
n.reserve(rootName.size() + 1 + name.size());
|
||||
n += rootName;
|
||||
n += ".";
|
||||
n += name;
|
||||
prop.documentationSymbol = std::move(n);
|
||||
}
|
||||
}
|
||||
else if (ExternType* etv = getMutable<ExternType>(ty))
|
||||
{
|
||||
for (auto& [name, prop] : etv->props)
|
||||
{
|
||||
prop.documentationSymbol = rootName + "." + name;
|
||||
std::string n;
|
||||
n.reserve(rootName.size() + 1 + name.size());
|
||||
n += rootName;
|
||||
n += ".";
|
||||
n += name;
|
||||
prop.documentationSymbol = std::move(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +176,15 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S
|
|||
for (const auto& [name, ty] : checkedModule->declaredGlobals)
|
||||
{
|
||||
TypeId globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/global/" + name;
|
||||
|
||||
static constexpr const char infix[] = "/global/";
|
||||
constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator
|
||||
std::string documentationSymbol;
|
||||
documentationSymbol.reserve(packageName.size() + infixLength + name.size());
|
||||
documentationSymbol += packageName;
|
||||
documentationSymbol += infix;
|
||||
documentationSymbol += name;
|
||||
|
||||
generateDocumentationSymbols(globalTy, documentationSymbol);
|
||||
targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol};
|
||||
|
||||
|
@ -178,7 +194,15 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S
|
|||
for (const auto& [name, ty] : checkedModule->exportedTypeBindings)
|
||||
{
|
||||
TypeFun globalTy = clone(ty, globals.globalTypes, cloneState);
|
||||
std::string documentationSymbol = packageName + "/globaltype/" + name;
|
||||
|
||||
static constexpr const char infix[] = "/globaltype/";
|
||||
constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator
|
||||
std::string documentationSymbol;
|
||||
documentationSymbol.reserve(packageName.size() + infixLength + name.size());
|
||||
documentationSymbol += packageName;
|
||||
documentationSymbol += infix;
|
||||
documentationSymbol += name;
|
||||
|
||||
generateDocumentationSymbols(globalTy.type, documentationSymbol);
|
||||
targetScope->exportedTypeBindings[name] = globalTy;
|
||||
|
||||
|
@ -224,7 +248,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile(
|
|||
namespace
|
||||
{
|
||||
|
||||
static ErrorVec accumulateErrors(
|
||||
ErrorVec accumulateErrors(
|
||||
const std::unordered_map<ModuleName, std::shared_ptr<SourceNode>>& sourceNodes,
|
||||
ModuleResolver& moduleResolver,
|
||||
const ModuleName& name
|
||||
|
@ -277,7 +301,7 @@ static ErrorVec accumulateErrors(
|
|||
return result;
|
||||
}
|
||||
|
||||
static void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
|
||||
void filterLintOptions(LintOptions& lintOptions, const std::vector<HotComment>& hotcomments, Mode mode)
|
||||
{
|
||||
uint64_t ignoreLints = LintWarning::parseMask(hotcomments);
|
||||
|
||||
|
@ -369,7 +393,10 @@ std::vector<RequireCycle> getRequireCycles(
|
|||
|
||||
if (!cycle.empty())
|
||||
{
|
||||
result.push_back({depLocation, std::move(cycle)});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
result.emplace_back(RequireCycle{depLocation, std::move(cycle)});
|
||||
else
|
||||
result.push_back({depLocation, std::move(cycle)});
|
||||
|
||||
// note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start
|
||||
// so it's safe to *only* clear seen vector when we find a cycle
|
||||
|
@ -402,7 +429,7 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c
|
|||
{
|
||||
}
|
||||
|
||||
void Frontend::setLuauSolverSelectionFromWorkspace(SolverMode mode)
|
||||
void Frontend::setLuauSolverMode(SolverMode mode)
|
||||
{
|
||||
useNewLuauSolver.store(mode);
|
||||
}
|
||||
|
@ -1029,8 +1056,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec);
|
||||
|
||||
item.stats.timeCheck += duration;
|
||||
item.stats.filesStrict += mode == Mode::Strict;
|
||||
item.stats.filesNonstrict += mode == Mode::Nonstrict;
|
||||
item.stats.filesStrict += (mode == Mode::Strict) ? 1 : 0;
|
||||
item.stats.filesNonstrict += (mode == Mode::Nonstrict) ? 1 : 0;
|
||||
|
||||
if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats)
|
||||
{
|
||||
|
@ -1103,7 +1130,10 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
|
|||
ErrorVec parseErrors;
|
||||
|
||||
for (const ParseError& pe : sourceModule.parseErrors)
|
||||
parseErrors.push_back(TypeError{pe.getLocation(), item.name, SyntaxError{pe.what()}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
parseErrors.emplace_back(pe.getLocation(), item.name, SyntaxError{pe.what()});
|
||||
else
|
||||
parseErrors.push_back(TypeError{pe.getLocation(), item.name, SyntaxError{pe.what()}});
|
||||
|
||||
module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end());
|
||||
|
||||
|
@ -1333,7 +1363,7 @@ SourceModule* Frontend::getSourceModule(const ModuleName& moduleName)
|
|||
|
||||
const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const
|
||||
{
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||
}
|
||||
|
||||
struct InternalTypeFinder : TypeOnceVisitor
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure)
|
||||
LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -334,7 +336,7 @@ struct FreeTypeSearcher : TypeVisitor
|
|||
NotNull<DenseHashSet<TypeId>> cachedTypes;
|
||||
|
||||
explicit FreeTypeSearcher(NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes)
|
||||
: TypeVisitor("FreeTypeSearcher", /*skipBoundTypes*/ true)
|
||||
: TypeVisitor("FreeTypeSearcher", /* skipBoundTypes */ true)
|
||||
, scope(scope)
|
||||
, cachedTypes(cachedTypes)
|
||||
{
|
||||
|
@ -622,38 +624,56 @@ struct TypeCacher : TypeOnceVisitor
|
|||
DenseHashSet<TypePackId> uncacheablePacks{nullptr};
|
||||
|
||||
explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes)
|
||||
: TypeOnceVisitor("TypeCacher", /* skipBoundTypes */ false)
|
||||
: TypeOnceVisitor("TypeCacher", /* skipBoundTypes */ FFlag::LuauReduceSetTypeStackPressure)
|
||||
, cachedTypes(cachedTypes)
|
||||
{
|
||||
}
|
||||
|
||||
void cache(TypeId ty) const
|
||||
{
|
||||
cachedTypes->insert(ty);
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
cachedTypes->insert(follow(ty));
|
||||
else
|
||||
cachedTypes->insert(ty);
|
||||
}
|
||||
|
||||
bool isCached(TypeId ty) const
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return cachedTypes->contains(follow(ty));
|
||||
|
||||
return cachedTypes->contains(ty);
|
||||
}
|
||||
|
||||
void markUncacheable(TypeId ty)
|
||||
{
|
||||
uncacheable.insert(ty);
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
uncacheable.insert(follow(ty));
|
||||
else
|
||||
uncacheable.insert(ty);
|
||||
}
|
||||
|
||||
void markUncacheable(TypePackId tp)
|
||||
{
|
||||
uncacheablePacks.insert(tp);
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
uncacheablePacks.insert(follow(tp));
|
||||
else
|
||||
uncacheablePacks.insert(tp);
|
||||
}
|
||||
|
||||
bool isUncacheable(TypeId ty) const
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return uncacheable.contains(follow(ty));
|
||||
|
||||
return uncacheable.contains(ty);
|
||||
}
|
||||
|
||||
bool isUncacheable(TypePackId tp) const
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return uncacheablePacks.contains(follow(tp));
|
||||
|
||||
return uncacheablePacks.contains(tp);
|
||||
}
|
||||
|
||||
|
@ -668,6 +688,7 @@ struct TypeCacher : TypeOnceVisitor
|
|||
|
||||
bool visit(TypeId ty, const BoundType& btv) override
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauReduceSetTypeStackPressure);
|
||||
traverse(btv.boundTo);
|
||||
if (isUncacheable(btv.boundTo))
|
||||
markUncacheable(ty);
|
||||
|
@ -1381,14 +1402,37 @@ struct GenericCounter : TypeVisitor
|
|||
|
||||
Polarity polarity = Polarity::Positive;
|
||||
|
||||
int depth = 0;
|
||||
bool hitLimits = false;
|
||||
|
||||
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
|
||||
: TypeVisitor("GenericCounter")
|
||||
, cachedTypes(cachedTypes)
|
||||
{
|
||||
}
|
||||
|
||||
void checkLimits()
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure && depth > FInt::LuauGenericCounterMaxDepth)
|
||||
hitLimits = true;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty) override
|
||||
{
|
||||
checkLimits();
|
||||
return !FFlag::LuauReduceSetTypeStackPressure || !hitLimits;
|
||||
}
|
||||
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
{
|
||||
std::optional<RecursionCounter> rc{std::nullopt};
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
rc.emplace(&depth);
|
||||
checkLimits();
|
||||
}
|
||||
|
||||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
|
@ -1408,6 +1452,13 @@ struct GenericCounter : TypeVisitor
|
|||
|
||||
bool visit(TypeId ty, const TableType& tt) override
|
||||
{
|
||||
std::optional<RecursionCounter> rc{std::nullopt};
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
rc.emplace(&depth);
|
||||
checkLimits();
|
||||
}
|
||||
|
||||
if (ty->persistent)
|
||||
return false;
|
||||
|
||||
|
@ -1542,13 +1593,16 @@ void pruneUnnecessaryGenerics(
|
|||
|
||||
counter.traverse(ty);
|
||||
|
||||
for (const auto& [generic, state] : counter.generics)
|
||||
if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits)
|
||||
{
|
||||
if (state.count == 1 && state.polarity != Polarity::Mixed)
|
||||
for (const auto& [generic, state] : counter.generics)
|
||||
{
|
||||
if (arena.get() != generic->owningArena)
|
||||
continue;
|
||||
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
|
||||
if (state.count == 1 && state.polarity != Polarity::Mixed)
|
||||
{
|
||||
if (arena.get() != generic->owningArena)
|
||||
continue;
|
||||
emplaceType<BoundType>(asMutable(generic), builtinTypes->unknownType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1564,9 +1618,12 @@ void pruneUnnecessaryGenerics(
|
|||
return true;
|
||||
seen.insert(ty);
|
||||
|
||||
auto state = counter.generics.find(ty);
|
||||
if (state && state->count == 0)
|
||||
return true;
|
||||
if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits)
|
||||
{
|
||||
auto state = counter.generics.find(ty);
|
||||
if (state && state->count == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return !get<GenericType>(ty);
|
||||
}
|
||||
|
@ -1574,12 +1631,17 @@ void pruneUnnecessaryGenerics(
|
|||
|
||||
functionTy->generics.erase(it, functionTy->generics.end());
|
||||
|
||||
for (const auto& [genericPack, state] : counter.genericPacks)
|
||||
|
||||
if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits)
|
||||
{
|
||||
if (state.count == 1)
|
||||
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
|
||||
for (const auto& [genericPack, state] : counter.genericPacks)
|
||||
{
|
||||
if (state.count == 1)
|
||||
emplaceTypePack<BoundTypePack>(asMutable(genericPack), builtinTypes->unknownTypePack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DenseHashSet<TypePackId> seen2{nullptr};
|
||||
auto it2 = std::remove_if(
|
||||
functionTy->genericPacks.begin(),
|
||||
|
@ -1591,9 +1653,12 @@ void pruneUnnecessaryGenerics(
|
|||
return true;
|
||||
seen2.insert(tp);
|
||||
|
||||
auto state = counter.genericPacks.find(tp);
|
||||
if (state && state->count == 0)
|
||||
return true;
|
||||
if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits)
|
||||
{
|
||||
auto state = counter.genericPacks.find(tp);
|
||||
if (state && state->count == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return !get<GenericTypePack>(tp);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "Luau/VisitType.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauInferPolarityOfReadWriteProperties)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -64,6 +65,11 @@ TypeId Instantiation::clean(TypeId ty)
|
|||
clone.magic = ftv->magic;
|
||||
clone.tags = ftv->tags;
|
||||
clone.argNames = ftv->argNames;
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
clone.isDeprecatedFunction = ftv->isDeprecatedFunction;
|
||||
clone.deprecatedInfo = ftv->deprecatedInfo;
|
||||
}
|
||||
TypeId result = addType(std::move(clone));
|
||||
|
||||
// Annoyingly, we have to do this even if there are no generics,
|
||||
|
|
|
@ -267,6 +267,8 @@ static void errorToString(std::ostream& stream, const T& err)
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, MultipleNonviableOverloads>)
|
||||
stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }";
|
||||
else if constexpr (std::is_same_v<T, RecursiveRestraintViolation>)
|
||||
stream << "RecursiveRestraintViolation";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -2293,7 +2294,16 @@ private:
|
|||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->local->name.value);
|
||||
{
|
||||
if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr)
|
||||
{
|
||||
report(node->location, node->local->name.value, *fty->deprecatedInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
report(node->location, node->local->name.value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2304,7 +2314,16 @@ private:
|
|||
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
|
||||
|
||||
if (shouldReport)
|
||||
report(node->location, node->name.value);
|
||||
{
|
||||
if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr)
|
||||
{
|
||||
report(node->location, node->name.value, *fty->deprecatedInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
report(node->location, node->name.value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2380,8 +2399,14 @@ private:
|
|||
className = global->name.value;
|
||||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr)
|
||||
{
|
||||
report(node->location, className, functionName, *fty->deprecatedInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2415,7 +2440,14 @@ private:
|
|||
|
||||
const char* functionName = node->index.value;
|
||||
|
||||
report(node->location, className, functionName);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr)
|
||||
{
|
||||
report(node->location, className, functionName, *fty->deprecatedInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
report(node->location, className, functionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2443,7 +2475,6 @@ private:
|
|||
|
||||
const FunctionType* fty = getFunctionType(func);
|
||||
bool isDeprecated = fty && fty->isDeprecatedFunction;
|
||||
|
||||
// If a function is deprecated, we don't want to flag its recursive uses.
|
||||
// So we push it on a stack while its body is being analyzed.
|
||||
// When a deprecated function is used, we check the stack to ensure that we are not inside that function.
|
||||
|
@ -2474,11 +2505,47 @@ private:
|
|||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* tableName, const char* functionName, const AstAttr::DeprecatedInfo& info)
|
||||
{
|
||||
std::string usePart = info.use ? format(", use '%s' instead", info.use->c_str()) : "";
|
||||
std::string reasonPart = info.reason ? format(". %s", info.reason->c_str()) : "";
|
||||
if (tableName)
|
||||
emitWarning(
|
||||
*context,
|
||||
LintWarning::Code_DeprecatedApi,
|
||||
location,
|
||||
"Member '%s.%s' is deprecated%s%s",
|
||||
tableName,
|
||||
functionName,
|
||||
usePart.c_str(),
|
||||
reasonPart.c_str()
|
||||
);
|
||||
else
|
||||
emitWarning(
|
||||
*context,
|
||||
LintWarning::Code_DeprecatedApi,
|
||||
location,
|
||||
"Member '%s' is deprecated%s%s",
|
||||
functionName,
|
||||
usePart.c_str(),
|
||||
reasonPart.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* functionName)
|
||||
{
|
||||
emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName);
|
||||
}
|
||||
|
||||
void report(const Location& location, const char* functionName, const AstAttr::DeprecatedInfo& info)
|
||||
{
|
||||
std::string usePart = info.use ? format(", use '%s' instead", info.use->c_str()) : "";
|
||||
std::string reasonPart = info.reason ? format(". %s", info.reason->c_str()) : "";
|
||||
emitWarning(
|
||||
*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated%s%s", functionName, usePart.c_str(), reasonPart.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<const FunctionType*> functionTypeScopeStack;
|
||||
|
||||
void pushScope(const FunctionType* fty)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -270,7 +271,11 @@ struct ClonePublicInterface : Substitution
|
|||
}
|
||||
else
|
||||
{
|
||||
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
|
||||
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
module->errors.emplace_back(module->scopes[0].first, UnificationTooComplex{});
|
||||
else
|
||||
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
|
||||
return builtinTypes->errorType;
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +289,10 @@ struct ClonePublicInterface : Substitution
|
|||
}
|
||||
else
|
||||
{
|
||||
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
module->errors.emplace_back(module->scopes[0].first, UnificationTooComplex{});
|
||||
else
|
||||
module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}});
|
||||
return builtinTypes->errorTypePack;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "Luau/Common.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Error.h"
|
||||
|
@ -17,7 +16,6 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
@ -25,6 +23,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
|
|||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -42,7 +41,10 @@ struct StackPusher
|
|||
: stack(&stack)
|
||||
, scope(scope)
|
||||
{
|
||||
stack.push_back(NotNull{scope});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
stack.emplace_back(scope);
|
||||
else
|
||||
stack.push_back(NotNull{scope});
|
||||
}
|
||||
|
||||
~StackPusher()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
|
||||
|
@ -22,9 +23,9 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
|
|||
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNormalizationLimitTyvarUnionSize)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -3346,11 +3347,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
{
|
||||
result.reserve(result.size() + norm.tables.size());
|
||||
for (auto table : norm.tables)
|
||||
{
|
||||
if (!FFlag::LuauRefineTablesWithReadType)
|
||||
makeTableShared(table);
|
||||
result.push_back(table);
|
||||
}
|
||||
}
|
||||
else
|
||||
result.insert(result.end(), norm.tables.begin(), norm.tables.end());
|
||||
|
@ -3360,7 +3357,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
|
|||
if (get<NeverType>(intersect->tops))
|
||||
{
|
||||
TypeId ty = typeFromNormal(*intersect);
|
||||
result.push_back(arena->addType(IntersectionType{{tyvar, ty}}));
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
result.push_back(addIntersection(NotNull{arena}, builtinTypes, {tyvar, ty}));
|
||||
else
|
||||
result.push_back(arena->addType(IntersectionType{{tyvar, ty}}));
|
||||
}
|
||||
else
|
||||
result.push_back(tyvar);
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauLimitUnification)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -47,7 +49,23 @@ std::pair<OverloadResolver::Analysis, TypeId> OverloadResolver::selectOverload(T
|
|||
{
|
||||
Subtyping::Variance variance = subtyping.variance;
|
||||
subtyping.variance = Subtyping::Variance::Contravariant;
|
||||
SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes, scope);
|
||||
SubtypingResult r;
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
std::vector<TypeId> generics;
|
||||
generics.reserve(ftv->generics.size());
|
||||
for (TypeId g : ftv->generics)
|
||||
{
|
||||
g = follow(g);
|
||||
if (get<GenericType>(g))
|
||||
generics.emplace_back(g);
|
||||
}
|
||||
r = subtyping.isSubtype(
|
||||
argsPack, ftv->argTypes, scope, !generics.empty() ? std::optional<std::vector<TypeId>>{generics} : std::nullopt
|
||||
);
|
||||
}
|
||||
else
|
||||
r = subtyping.isSubtype(argsPack, ftv->argTypes, scope);
|
||||
subtyping.variance = variance;
|
||||
|
||||
if (!useFreeTypeBounds && !r.assumedConstraints.empty())
|
||||
|
@ -584,7 +602,7 @@ SolveResult solveFunctionCall(
|
|||
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack});
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
|
||||
|
||||
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
|
||||
const UnifyResult unifyResult = u2.unify(*overloadToUse, inferredTy);
|
||||
|
||||
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
|
||||
{
|
||||
|
@ -598,8 +616,23 @@ SolveResult solveFunctionCall(
|
|||
resultPack = *subst;
|
||||
}
|
||||
|
||||
if (!occursCheckPassed)
|
||||
return {SolveResult::OccursCheckFailed};
|
||||
if (FFlag::LuauLimitUnification)
|
||||
{
|
||||
switch (unifyResult)
|
||||
{
|
||||
case Luau::UnifyResult::Ok:
|
||||
break;
|
||||
case Luau::UnifyResult::OccursCheckFailed:
|
||||
return {SolveResult::CodeTooComplex};
|
||||
case Luau::UnifyResult::TooComplex:
|
||||
return {SolveResult::OccursCheckFailed};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unifyResult != UnifyResult::Ok)
|
||||
return {SolveResult::OccursCheckFailed};
|
||||
}
|
||||
|
||||
SolveResult result;
|
||||
result.result = SolveResult::Ok;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic)
|
||||
LUAU_FASTFLAGVARIABLE(LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -265,6 +267,19 @@ bool Scope::shouldWarnGlobal(std::string name) const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Scope::isInvalidTypeAliasName(const std::string& name) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauNameConstraintRestrictRecursiveTypes);
|
||||
|
||||
for (auto scope = this; scope; scope = scope->parent.get())
|
||||
{
|
||||
if (scope->invalidTypeAliasNames.contains(name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NotNull<Scope> Scope::findNarrowestScopeContaining(Location location)
|
||||
{
|
||||
Scope* bestScope = this;
|
||||
|
@ -290,6 +305,12 @@ NotNull<Scope> Scope::findNarrowestScopeContaining(Location location)
|
|||
|
||||
bool subsumesStrict(Scope* left, Scope* right)
|
||||
{
|
||||
if (FFlag::LuauNoScopeShallNotSubsumeAll)
|
||||
{
|
||||
if (!left || !right)
|
||||
return false;
|
||||
}
|
||||
|
||||
while (right)
|
||||
{
|
||||
if (right->parent.get() == left)
|
||||
|
@ -303,6 +324,12 @@ bool subsumesStrict(Scope* left, Scope* right)
|
|||
|
||||
bool subsumes(Scope* left, Scope* right)
|
||||
{
|
||||
if (FFlag::LuauNoScopeShallNotSubsumeAll)
|
||||
{
|
||||
if (!left || !right)
|
||||
return false;
|
||||
}
|
||||
|
||||
return left == right || subsumesStrict(left, right);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Luau/Simplify.h"
|
||||
|
||||
#include "Luau/BuiltinDefinitions.h"
|
||||
#include "Luau/Clone.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/DenseHash.h"
|
||||
|
@ -18,10 +19,10 @@
|
|||
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8)
|
||||
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate)
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128)
|
||||
LUAU_FASTFLAG(LuauRefineDistributesOverUnions)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -288,7 +289,7 @@ Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
);
|
||||
|
||||
if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
|
||||
return FFlag::LuauRelateTablesAreNeverDisjoint ? Relation::Intersects : Relation::Disjoint;
|
||||
return Relation::Intersects;
|
||||
|
||||
const auto [propName, rightProp] = *begin(rightTable->props);
|
||||
|
||||
|
@ -587,17 +588,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
{
|
||||
if (auto propInExternType = re->props.find(name); propInExternType != re->props.end())
|
||||
{
|
||||
Relation propRel;
|
||||
if (FFlag::LuauMissingSeenSetRelate)
|
||||
{
|
||||
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
|
||||
propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
|
||||
propRel = relate(*prop.readTy, *propInExternType->second.readTy);
|
||||
}
|
||||
LUAU_ASSERT(prop.readTy && propInExternType->second.readTy);
|
||||
Relation propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen);
|
||||
|
||||
if (propRel == Relation::Disjoint)
|
||||
return Relation::Disjoint;
|
||||
|
@ -630,7 +622,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
|||
return Relation::Disjoint;
|
||||
}
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType && is<TableType>(right))
|
||||
if (is<TableType>(right))
|
||||
{
|
||||
// FIXME: This could be better in that we can say a table only
|
||||
// intersects with an extern type if they share a property, but
|
||||
|
@ -784,10 +776,42 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right)
|
|||
LUAU_ASSERT(leftUnion);
|
||||
|
||||
bool changed = false;
|
||||
std::set<TypeId> newParts;
|
||||
|
||||
size_t maxSize = DFInt::LuauSimplificationComplexityLimit;
|
||||
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
if (leftUnion->options.size() > maxSize)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
|
||||
UnionBuilder ub(arena, builtinTypes);
|
||||
ub.reserve(leftUnion->options.size());
|
||||
|
||||
for (TypeId part : leftUnion)
|
||||
{
|
||||
TypeId simplified = intersect(right, part);
|
||||
changed |= simplified != part;
|
||||
|
||||
if (get<NeverType>(simplified))
|
||||
{
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
ub.add(simplified);
|
||||
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
if (ub.size() > maxSize)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return left;
|
||||
|
||||
return ub.build();
|
||||
}
|
||||
|
||||
std::set<TypeId> newParts;
|
||||
|
||||
if (leftUnion->options.size() > maxSize)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
|
||||
|
@ -838,6 +862,26 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right)
|
|||
if (optionSize > maxSize)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
UnionBuilder ub{arena, builtinTypes};
|
||||
for (TypeId leftPart : leftUnion)
|
||||
{
|
||||
for (TypeId rightPart : rightUnion)
|
||||
{
|
||||
TypeId simplified = intersect(leftPart, rightPart);
|
||||
|
||||
ub.add(simplified);
|
||||
|
||||
// Initial combination size check could not predict nested union iteration
|
||||
if (ub.size() > maxSize)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
}
|
||||
}
|
||||
|
||||
return ub.build();
|
||||
}
|
||||
|
||||
for (TypeId leftPart : leftUnion)
|
||||
{
|
||||
for (TypeId rightPart : rightUnion)
|
||||
|
@ -933,14 +977,11 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithTruthy(TypeId target) co
|
|||
{
|
||||
target = follow(target);
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
{
|
||||
if (isApproximatelyTruthyType(target))
|
||||
return target;
|
||||
if (isApproximatelyTruthyType(target))
|
||||
return target;
|
||||
|
||||
if (isApproximatelyFalsyType(target))
|
||||
return builtinTypes->neverType;
|
||||
}
|
||||
if (isApproximatelyFalsyType(target))
|
||||
return builtinTypes->neverType;
|
||||
|
||||
if (is<UnknownType>(target))
|
||||
return builtinTypes->truthyType;
|
||||
|
@ -978,14 +1019,11 @@ std::optional<TypeId> TypeSimplifier::basicIntersectWithFalsy(TypeId target) con
|
|||
{
|
||||
target = follow(target);
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
{
|
||||
if (isApproximatelyTruthyType(target))
|
||||
return builtinTypes->neverType;
|
||||
if (isApproximatelyTruthyType(target))
|
||||
return builtinTypes->neverType;
|
||||
|
||||
if (isApproximatelyFalsyType(target))
|
||||
return target;
|
||||
}
|
||||
if (isApproximatelyFalsyType(target))
|
||||
return target;
|
||||
|
||||
if (is<NeverType, ErrorType>(target))
|
||||
return target;
|
||||
|
@ -1033,7 +1071,6 @@ TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right)
|
|||
{
|
||||
// ~(A | B) & C
|
||||
// (~A & C) & (~B & C)
|
||||
|
||||
bool changed = false;
|
||||
std::set<TypeId> newParts;
|
||||
|
||||
|
@ -1196,7 +1233,12 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right)
|
|||
LUAU_ASSERT(leftIntersection);
|
||||
|
||||
if (leftIntersection->parts.size() > (size_t)DFInt::LuauSimplificationComplexityLimit)
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
{
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
else
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
std::set<TypeId> newParts;
|
||||
|
@ -1355,42 +1397,21 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (FFlag::LuauRefineTablesWithReadType)
|
||||
{
|
||||
if (isApproximatelyTruthyType(left))
|
||||
if (auto res = basicIntersectWithTruthy(right))
|
||||
return res;
|
||||
if (isApproximatelyTruthyType(left))
|
||||
if (auto res = basicIntersectWithTruthy(right))
|
||||
return res;
|
||||
|
||||
if (isApproximatelyTruthyType(right))
|
||||
if (auto res = basicIntersectWithTruthy(left))
|
||||
return res;
|
||||
if (isApproximatelyTruthyType(right))
|
||||
if (auto res = basicIntersectWithTruthy(left))
|
||||
return res;
|
||||
|
||||
if (isApproximatelyFalsyType(left))
|
||||
if (auto res = basicIntersectWithFalsy(right))
|
||||
return res;
|
||||
if (isApproximatelyFalsyType(left))
|
||||
if (auto res = basicIntersectWithFalsy(right))
|
||||
return res;
|
||||
|
||||
if (isApproximatelyFalsyType(right))
|
||||
if (auto res = basicIntersectWithFalsy(left))
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isTruthyType_DEPRECATED(left))
|
||||
if (auto res = basicIntersectWithTruthy(right))
|
||||
return res;
|
||||
|
||||
if (isTruthyType_DEPRECATED(right))
|
||||
if (auto res = basicIntersectWithTruthy(left))
|
||||
return res;
|
||||
|
||||
if (isFalsyType_DEPRECATED(left))
|
||||
if (auto res = basicIntersectWithFalsy(right))
|
||||
return res;
|
||||
|
||||
if (isFalsyType_DEPRECATED(right))
|
||||
if (auto res = basicIntersectWithFalsy(left))
|
||||
return res;
|
||||
}
|
||||
if (isApproximatelyFalsyType(right))
|
||||
if (auto res = basicIntersectWithFalsy(left))
|
||||
return res;
|
||||
|
||||
|
||||
Relation relation = relate(left, right);
|
||||
|
@ -1458,13 +1479,19 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
|
|||
if (isTypeVariable(left))
|
||||
{
|
||||
blockedTypes.insert(left);
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
else
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
|
||||
if (isTypeVariable(right))
|
||||
{
|
||||
blockedTypes.insert(right);
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
return addIntersection(arena, builtinTypes, {left, right});
|
||||
else
|
||||
return arena->addType(IntersectionType{{left, right}});
|
||||
}
|
||||
|
||||
if (auto ut = get<UnionType>(left))
|
||||
|
@ -1514,6 +1541,47 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right)
|
|||
if (auto leftUnion = get<UnionType>(left))
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (FFlag::LuauReduceSetTypeStackPressure)
|
||||
{
|
||||
UnionBuilder ub(arena, builtinTypes);
|
||||
ub.reserve(leftUnion->options.size());
|
||||
for (TypeId part : leftUnion)
|
||||
{
|
||||
if (get<NeverType>(part))
|
||||
{
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Relation r = relate(part, right);
|
||||
switch (r)
|
||||
{
|
||||
case Relation::Coincident:
|
||||
case Relation::Superset:
|
||||
return left;
|
||||
case Relation::Subset:
|
||||
ub.add(right);
|
||||
changed = true;
|
||||
break;
|
||||
default:
|
||||
ub.add(part);
|
||||
ub.add(right);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return left;
|
||||
|
||||
// If the left-side is changed but has no parts, then the left-side union is uninhabited.
|
||||
if (ub.size() == 0)
|
||||
return right;
|
||||
|
||||
return ub.build();
|
||||
}
|
||||
|
||||
std::set<TypeId> newParts;
|
||||
for (TypeId part : leftUnion)
|
||||
{
|
||||
|
@ -1578,6 +1646,44 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right)
|
|||
}
|
||||
}
|
||||
|
||||
if (FFlag::LuauRefineDistributesOverUnions)
|
||||
{
|
||||
if (const auto [lt, rt] = get2<TableType, TableType>(left, right); lt && rt)
|
||||
{
|
||||
if (1 == lt->props.size() && 1 == rt->props.size())
|
||||
{
|
||||
const auto [propName, leftProp] = *begin(lt->props);
|
||||
const auto [rightPropName, rightProp] = *begin(rt->props);
|
||||
|
||||
if (rightPropName != propName)
|
||||
return arena->addType(UnionType{{left, right}});
|
||||
|
||||
if (leftProp.readTy && rightProp.readTy)
|
||||
{
|
||||
Relation r = relate(*leftProp.readTy, *rightProp.readTy);
|
||||
|
||||
switch (r)
|
||||
{
|
||||
case Relation::Disjoint:
|
||||
{
|
||||
TableType result;
|
||||
result.state = TableState::Sealed;
|
||||
result.props[propName] = union_(*leftProp.readTy, *rightProp.readTy);
|
||||
return arena->addType(result);
|
||||
}
|
||||
case Relation::Superset:
|
||||
case Relation::Coincident:
|
||||
return left;
|
||||
case Relation::Subset:
|
||||
return right;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arena->addType(UnionType{{left, right}});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -92,6 +93,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
|
|||
clone.argNames = a.argNames;
|
||||
clone.isCheckedFunction = a.isCheckedFunction;
|
||||
clone.isDeprecatedFunction = a.isDeprecatedFunction;
|
||||
clone.deprecatedInfo = a.deprecatedInfo;
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, TableType>)
|
||||
|
@ -299,7 +301,10 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
|
|||
if (fresh)
|
||||
{
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({ty, nullptr, false, false, index});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
nodes.emplace_back(ty, nullptr, false, false, index);
|
||||
else
|
||||
nodes.push_back({ty, nullptr, false, false, index});
|
||||
}
|
||||
|
||||
return {index, fresh};
|
||||
|
@ -314,7 +319,10 @@ std::pair<int, bool> Tarjan::indexify(TypePackId tp)
|
|||
if (fresh)
|
||||
{
|
||||
index = int(nodes.size());
|
||||
nodes.push_back({nullptr, tp, false, false, index});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
nodes.emplace_back(nullptr, tp, false, false, index);
|
||||
else
|
||||
nodes.push_back({nullptr, tp, false, false, index});
|
||||
}
|
||||
|
||||
return {index, fresh};
|
||||
|
@ -384,7 +392,10 @@ TarjanResult Tarjan::loop()
|
|||
{
|
||||
// Original recursion point, update the parent continuation point and start the new element
|
||||
worklist.back() = {index, currEdge + 1, lastEdge};
|
||||
worklist.push_back({childIndex, -1, -1});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
worklist.emplace_back(childIndex, -1, -1);
|
||||
else
|
||||
worklist.push_back({childIndex, -1, -1});
|
||||
|
||||
// We need to continue the top-level loop from the start with the new worklist element
|
||||
foundFresh = true;
|
||||
|
@ -442,7 +453,10 @@ TarjanResult Tarjan::visitRoot(TypeId ty)
|
|||
ty = log->follow(ty);
|
||||
|
||||
auto [index, fresh] = indexify(ty);
|
||||
worklist.push_back({index, -1, -1});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
worklist.emplace_back(index, -1, -1);
|
||||
else
|
||||
worklist.push_back({index, -1, -1});
|
||||
return loop();
|
||||
}
|
||||
|
||||
|
@ -455,7 +469,10 @@ TarjanResult Tarjan::visitRoot(TypePackId tp)
|
|||
tp = log->follow(tp);
|
||||
|
||||
auto [index, fresh] = indexify(tp);
|
||||
worklist.push_back({index, -1, -1});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
worklist.emplace_back(index, -1, -1);
|
||||
else
|
||||
worklist.push_back({index, -1, -1});
|
||||
return loop();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,17 +13,19 @@
|
|||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFunction.h"
|
||||
#include "Luau/TypeIds.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypePath.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
|
||||
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -301,9 +303,12 @@ struct ApplyMappedGenerics : Substitution
|
|||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
// TODO: make this NotNull when LuauSubtypingGenericsDoesntUseVariance is clipped
|
||||
InternalErrorReporter* iceReporter;
|
||||
|
||||
SubtypingEnvironment& env;
|
||||
|
||||
// TODO: Clip with LuauSubtypingGenericsDoesntUseVariance
|
||||
ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, SubtypingEnvironment& env)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
|
@ -312,6 +317,20 @@ struct ApplyMappedGenerics : Substitution
|
|||
{
|
||||
}
|
||||
|
||||
ApplyMappedGenerics(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
SubtypingEnvironment& env,
|
||||
NotNull<InternalErrorReporter> iceReporter
|
||||
)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
, iceReporter(iceReporter.get())
|
||||
, env(env)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return env.containsMappedType(ty);
|
||||
|
@ -324,15 +343,84 @@ struct ApplyMappedGenerics : Substitution
|
|||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
const auto& bounds = env.getMappedTypeBounds(ty);
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
const auto& [lowerBound, upperBound] = env.getMappedTypeBounds(ty, NotNull{iceReporter});
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
if (upperBound.empty() && lowerBound.empty())
|
||||
{
|
||||
// No bounds for the generic we're mapping.
|
||||
// In this case, unknown vs never is an arbitrary choice:
|
||||
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
|
||||
// We choose unknown here, since it's closest to the original behavior.
|
||||
return builtinTypes->unknownType;
|
||||
}
|
||||
else if (!upperBound.empty())
|
||||
{
|
||||
TypeIds boundsToUse;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
for (TypeId ub : upperBound)
|
||||
{
|
||||
// quick and dirty check to avoid adding generic types
|
||||
if (!get<GenericType>(ub))
|
||||
boundsToUse.insert(ub);
|
||||
}
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
if (boundsToUse.empty())
|
||||
{
|
||||
// This case happens when we've collected no bounds for the generic we're mapping.
|
||||
// In this case, unknown vs never is an arbitrary choice:
|
||||
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
|
||||
// We choose unknown here, since it's closest to the original behavior.
|
||||
return builtinTypes->unknownType;
|
||||
}
|
||||
if (boundsToUse.size() == 1)
|
||||
return *boundsToUse.begin();
|
||||
|
||||
return arena->addType(IntersectionType{boundsToUse.take()});
|
||||
}
|
||||
else if (!lowerBound.empty())
|
||||
{
|
||||
TypeIds boundsToUse;
|
||||
|
||||
for (TypeId lb : lowerBound)
|
||||
{
|
||||
// quick and dirty check to avoid adding generic types
|
||||
if (!get<GenericType>(lb))
|
||||
boundsToUse.insert(lb);
|
||||
}
|
||||
|
||||
if (boundsToUse.empty())
|
||||
{
|
||||
// This case happens when we've collected no bounds for the generic we're mapping.
|
||||
// In this case, unknown vs never is an arbitrary choice:
|
||||
// ie, does it matter if we map add<A, A> to add<unknown, unknown> or add<never, never> in the context of subtyping?
|
||||
// We choose unknown here, since it's closest to the original behavior.
|
||||
return builtinTypes->unknownType;
|
||||
}
|
||||
else if (lowerBound.size() == 1)
|
||||
return *boundsToUse.begin();
|
||||
else
|
||||
return arena->addType(UnionType{boundsToUse.take()});
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(!"Unreachable path");
|
||||
return builtinTypes->unknownType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& bounds = env.getMappedTypeBounds_DEPRECATED(ty);
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
|
@ -350,6 +438,19 @@ struct ApplyMappedGenerics : Substitution
|
|||
if (get<ExternType>(ty))
|
||||
return true;
|
||||
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
if (const FunctionType* f = get<FunctionType>(ty))
|
||||
{
|
||||
for (TypeId g : f->generics)
|
||||
{
|
||||
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env.mappedGenerics.find(g); bounds && !bounds->empty())
|
||||
// We don't want to mutate the generics of a function that's being subtyped
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ty->persistent;
|
||||
}
|
||||
bool ignoreChildren(TypePackId ty) override
|
||||
|
@ -358,7 +459,19 @@ struct ApplyMappedGenerics : Substitution
|
|||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(
|
||||
NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<TypeArena> arena,
|
||||
TypeId ty,
|
||||
NotNull<InternalErrorReporter> iceReporter
|
||||
)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, *this, iceReporter};
|
||||
return amg.substitute(ty);
|
||||
}
|
||||
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics_DEPRECATED(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
|
||||
{
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, *this};
|
||||
return amg.substitute(ty);
|
||||
|
@ -377,7 +490,12 @@ const TypeId* SubtypingEnvironment::tryFindSubstitution(TypeId ty) const
|
|||
|
||||
const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair<TypeId, TypeId> subAndSuper) const
|
||||
{
|
||||
if (auto it = ephemeralCache.find(subAndSuper))
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
if (const auto it = seenSetCache.find(subAndSuper))
|
||||
return it;
|
||||
}
|
||||
else if (auto it = ephemeralCache.find(subAndSuper))
|
||||
return it;
|
||||
|
||||
if (parent)
|
||||
|
@ -388,13 +506,27 @@ const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair<Ty
|
|||
|
||||
bool SubtypingEnvironment::containsMappedType(TypeId ty) const
|
||||
{
|
||||
if (mappedGenerics.contains(ty))
|
||||
return true;
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (const auto bounds = mappedGenerics.find(ty); bounds && !bounds->empty())
|
||||
return true;
|
||||
|
||||
if (parent)
|
||||
return parent->containsMappedType(ty);
|
||||
if (parent)
|
||||
return parent->containsMappedType(ty);
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mappedGenerics_DEPRECATED.contains(ty))
|
||||
return true;
|
||||
|
||||
if (parent)
|
||||
return parent->containsMappedType(ty);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const
|
||||
|
@ -408,16 +540,32 @@ bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const
|
|||
return false;
|
||||
}
|
||||
|
||||
SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty)
|
||||
SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty, NotNull<InternalErrorReporter> iceReporter)
|
||||
{
|
||||
if (auto it = mappedGenerics.find(ty))
|
||||
LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
ty = follow(ty);
|
||||
std::vector<GenericBounds>* bounds = mappedGenerics.find(ty);
|
||||
if (bounds && !bounds->empty())
|
||||
return bounds->back();
|
||||
|
||||
if (parent)
|
||||
return parent->getMappedTypeBounds(ty, iceReporter);
|
||||
|
||||
LUAU_ASSERT(!"Use containsMappedType before asking for bounds!");
|
||||
iceReporter->ice("Trying to access bounds for a type with no in-scope bounds");
|
||||
}
|
||||
|
||||
SubtypingEnvironment::GenericBounds_DEPRECATED& SubtypingEnvironment::getMappedTypeBounds_DEPRECATED(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
if (auto it = mappedGenerics_DEPRECATED.find(ty))
|
||||
return *it;
|
||||
|
||||
if (parent)
|
||||
return parent->getMappedTypeBounds(ty);
|
||||
return parent->getMappedTypeBounds_DEPRECATED(ty);
|
||||
|
||||
LUAU_ASSERT(!"Use containsMappedType before asking for bounds!");
|
||||
return mappedGenerics[ty];
|
||||
return mappedGenerics_DEPRECATED[ty];
|
||||
}
|
||||
|
||||
TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp)
|
||||
|
@ -463,44 +611,51 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||
return result;
|
||||
}
|
||||
|
||||
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
const auto& lb = bounds.lowerBound;
|
||||
const auto& ub = bounds.upperBound;
|
||||
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
||||
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
||||
|
||||
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
|
||||
// we say that the result is true if normalization failed because complex types are likely to be inhabited.
|
||||
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
|
||||
|
||||
if (!nt || res == NormalizationResult::HitLimits)
|
||||
result.normalizationTooComplex = true;
|
||||
else if (res == NormalizationResult::False)
|
||||
for (const auto& [_, bounds] : env.mappedGenerics)
|
||||
LUAU_ASSERT(bounds.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& [subTy, bounds] : env.mappedGenerics_DEPRECATED)
|
||||
{
|
||||
/* If the normalized upper bound we're mapping to a generic is
|
||||
* uninhabited, then we must consider the subtyping relation not to
|
||||
* hold.
|
||||
*
|
||||
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
|
||||
*
|
||||
* T appears in covariant position and would have to be both string
|
||||
* and number at once.
|
||||
*
|
||||
* No actual value is both a string and a number, so the test fails.
|
||||
*
|
||||
* TODO: We'll need to add explanitory context here.
|
||||
*/
|
||||
result.isSubtype = false;
|
||||
const auto& lb = bounds.lowerBound;
|
||||
const auto& ub = bounds.upperBound;
|
||||
TypeId lowerBound = makeAggregateType<UnionType>(lb, builtinTypes->neverType);
|
||||
TypeId upperBound = makeAggregateType<IntersectionType>(ub, builtinTypes->unknownType);
|
||||
|
||||
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
|
||||
// we say that the result is true if normalization failed because complex types are likely to be inhabited.
|
||||
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
|
||||
|
||||
if (!nt || res == NormalizationResult::HitLimits)
|
||||
result.normalizationTooComplex = true;
|
||||
else if (res == NormalizationResult::False)
|
||||
{
|
||||
/* If the normalized upper bound we're mapping to a generic is
|
||||
* uninhabited, then we must consider the subtyping relation not to
|
||||
* hold.
|
||||
*
|
||||
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
|
||||
*
|
||||
* T appears in covariant position and would have to be both string
|
||||
* and number at once.
|
||||
*
|
||||
* No actual value is both a string and a number, so the test fails.
|
||||
*
|
||||
* TODO: We'll need to add explanitory context here.
|
||||
*/
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
SubtypingEnvironment boundsEnv;
|
||||
boundsEnv.parent = &env;
|
||||
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
|
||||
|
||||
SubtypingEnvironment boundsEnv;
|
||||
boundsEnv.parent = &env;
|
||||
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
}
|
||||
|
||||
/* TODO: We presently don't store subtype test results in the persistent
|
||||
|
@ -526,9 +681,14 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
|
|||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope)
|
||||
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull<Scope> scope, std::optional<std::vector<TypeId>> bindableGenerics)
|
||||
{
|
||||
SubtypingEnvironment env;
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics)
|
||||
{
|
||||
for (TypeId g : *bindableGenerics)
|
||||
env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}};
|
||||
}
|
||||
|
||||
SubtypingResult result = isCovariantWith(env, subTp, superTp, scope);
|
||||
|
||||
|
@ -538,6 +698,24 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu
|
|||
result.mappedGenericPacks = std::move(env.mappedGenericPacks);
|
||||
}
|
||||
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics)
|
||||
{
|
||||
for (TypeId bg : *bindableGenerics)
|
||||
{
|
||||
bg = follow(bg);
|
||||
|
||||
LUAU_ASSERT(env.mappedGenerics.contains(bg));
|
||||
|
||||
if (const std::vector<SubtypingEnvironment::GenericBounds>* bounds = env.mappedGenerics.find(bg))
|
||||
{
|
||||
// Bounds should have exactly one entry
|
||||
LUAU_ASSERT(bounds->size() == 1);
|
||||
if (!bounds->empty())
|
||||
result.andAlso(checkGenericBounds(bounds->back(), env, scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -550,7 +728,7 @@ SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult resu
|
|||
|
||||
if (result.isCacheable)
|
||||
resultCache[p] = result;
|
||||
else
|
||||
else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
env.ephemeralCache[p] = result;
|
||||
|
||||
return result;
|
||||
|
@ -668,7 +846,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
* For now, we do the conservative thing and refuse to cache anything
|
||||
* that touches a cycle.
|
||||
*/
|
||||
return SubtypingResult{true, false, false};
|
||||
SubtypingResult res;
|
||||
res.isSubtype = true;
|
||||
res.isCacheable = false;
|
||||
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
env.seenSetCache[typePair] = res;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
SeenSetPopper ssp{&seenTypes, typePair};
|
||||
|
@ -728,13 +913,54 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
result = {false};
|
||||
else if (get<ErrorType>(subTy))
|
||||
result = {true};
|
||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||
else if (auto subTypeFunctionInstance = get<TypeFunctionInstanceType>(subTy);
|
||||
subTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
bool mappedGenericsApplied = false;
|
||||
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy, iceReporter))
|
||||
{
|
||||
mappedGenericsApplied = *substSubTy != subTy;
|
||||
subTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSubTy);
|
||||
}
|
||||
|
||||
result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope);
|
||||
result.isCacheable = !mappedGenericsApplied;
|
||||
}
|
||||
else if (auto superTypeFunctionInstance = get<TypeFunctionInstanceType>(superTy);
|
||||
superTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
bool mappedGenericsApplied = false;
|
||||
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy, iceReporter))
|
||||
{
|
||||
mappedGenericsApplied = *substSuperTy != superTy;
|
||||
superTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSuperTy);
|
||||
}
|
||||
|
||||
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
|
||||
result.isCacheable = !mappedGenericsApplied;
|
||||
}
|
||||
else if (FFlag::LuauSubtypingGenericsDoesntUseVariance && (get<GenericType>(subTy) || get<GenericType>(superTy)))
|
||||
{
|
||||
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
else if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
}
|
||||
else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get<GenericType>(subTy) && variance == Variance::Covariant)
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant)
|
||||
else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get<GenericType>(superTy) && variance == Variance::Contravariant)
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
|
@ -818,14 +1044,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
|||
}
|
||||
else if (auto subTypeFunctionInstance = get<TypeFunctionInstanceType>(subTy))
|
||||
{
|
||||
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy))
|
||||
LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
if (auto substSubTy = env.applyMappedGenerics_DEPRECATED(builtinTypes, arena, subTy))
|
||||
subTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSubTy);
|
||||
|
||||
result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope);
|
||||
}
|
||||
else if (auto superTypeFunctionInstance = get<TypeFunctionInstanceType>(superTy))
|
||||
{
|
||||
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy))
|
||||
LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
if (auto substSuperTy = env.applyMappedGenerics_DEPRECATED(builtinTypes, arena, superTy))
|
||||
superTypeFunctionInstance = get<TypeFunctionInstanceType>(*substSuperTy);
|
||||
|
||||
result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope);
|
||||
|
@ -1107,6 +1335,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
|||
{
|
||||
// <A...>(A...) -> number <: (...number) -> number
|
||||
bool ok = bindGeneric(env, *subTail, *superTail);
|
||||
|
||||
results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail));
|
||||
}
|
||||
else
|
||||
|
@ -1240,7 +1469,8 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
|
|||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull<Scope> scope)
|
||||
{
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope).andAlso(isContravariantWith(env, subTy, superTy, scope));
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy, scope);
|
||||
result.andAlso(isContravariantWith(env, subTy, superTy, scope));
|
||||
|
||||
if (result.reasoning.empty())
|
||||
result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant});
|
||||
|
@ -1750,6 +1980,23 @@ SubtypingResult Subtyping::isCovariantWith(
|
|||
)
|
||||
{
|
||||
SubtypingResult result;
|
||||
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty())
|
||||
{
|
||||
for (TypeId g : subFunction->generics)
|
||||
{
|
||||
g = follow(g);
|
||||
if (get<GenericType>(g))
|
||||
{
|
||||
if (auto bounds = env.mappedGenerics.find(g))
|
||||
// g may shadow an existing generic, so push a fresh set of bounds
|
||||
bounds->emplace_back();
|
||||
else
|
||||
env.mappedGenerics[g] = {SubtypingEnvironment::GenericBounds{}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
result.orElse(
|
||||
isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments)
|
||||
|
@ -1771,18 +2018,31 @@ SubtypingResult Subtyping::isCovariantWith(
|
|||
|
||||
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns));
|
||||
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes)
|
||||
{
|
||||
if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes)
|
||||
if (superFunction->generics.size() != subFunction->generics.size())
|
||||
result.andAlso({false}).withError(
|
||||
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
|
||||
);
|
||||
if (superFunction->genericPacks.size() != subFunction->genericPacks.size())
|
||||
result.andAlso({false}).withError(
|
||||
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
|
||||
);
|
||||
}
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty())
|
||||
{
|
||||
for (TypeId g : subFunction->generics)
|
||||
{
|
||||
if (superFunction->generics.size() != subFunction->generics.size())
|
||||
result.andAlso({false}).withError(
|
||||
TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}}
|
||||
);
|
||||
if (superFunction->genericPacks.size() != subFunction->genericPacks.size())
|
||||
result.andAlso({false}).withError(
|
||||
TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}}
|
||||
);
|
||||
g = follow(g);
|
||||
if (get<GenericType>(g))
|
||||
{
|
||||
auto bounds = env.mappedGenerics.find(g);
|
||||
LUAU_ASSERT(bounds && !bounds->empty());
|
||||
// Check the bounds are valid
|
||||
result.andAlso(checkGenericBounds(bounds->back(), env, scope));
|
||||
|
||||
bounds->pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2084,25 +2344,76 @@ SubtypingResult Subtyping::isCovariantWith(
|
|||
|
||||
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy)
|
||||
{
|
||||
if (variance == Variance::Covariant)
|
||||
if (FFlag::LuauSubtypingGenericsDoesntUseVariance)
|
||||
{
|
||||
if (!get<GenericType>(subTy))
|
||||
return false;
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
std::optional<SubtypingEnvironment::GenericBounds> originalSubTyBounds = std::nullopt;
|
||||
|
||||
if (!env.mappedGenerics.find(subTy) && env.containsMappedType(subTy))
|
||||
if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty())
|
||||
{
|
||||
LUAU_ASSERT(get<GenericType>(subTy));
|
||||
|
||||
originalSubTyBounds = SubtypingEnvironment::GenericBounds{subBounds->back()};
|
||||
|
||||
auto& [lowerSubBounds, upperSubBounds] = subBounds->back();
|
||||
|
||||
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
|
||||
{
|
||||
LUAU_ASSERT(get<GenericType>(superTy));
|
||||
|
||||
const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
|
||||
|
||||
maybeUpdateBounds(subTy, superTy, upperSubBounds, lowerSuperBounds, upperSuperBounds);
|
||||
}
|
||||
else
|
||||
upperSubBounds.insert(superTy);
|
||||
}
|
||||
else if (env.containsMappedType(subTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
|
||||
env.mappedGenerics[subTy].upperBound.insert(superTy);
|
||||
if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty())
|
||||
{
|
||||
LUAU_ASSERT(get<GenericType>(superTy));
|
||||
|
||||
auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back();
|
||||
|
||||
if (originalSubTyBounds)
|
||||
{
|
||||
LUAU_ASSERT(get<GenericType>(subTy));
|
||||
|
||||
const auto& [originalLowerSubBound, originalUpperSubBound] = *originalSubTyBounds;
|
||||
|
||||
maybeUpdateBounds(superTy, subTy, lowerSuperBounds, originalUpperSubBound, originalLowerSubBound);
|
||||
}
|
||||
else
|
||||
lowerSuperBounds.insert(subTy);
|
||||
}
|
||||
else if (env.containsMappedType(superTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get<GenericType>(superTy))
|
||||
return false;
|
||||
if (variance == Variance::Covariant)
|
||||
{
|
||||
if (!get<GenericType>(subTy))
|
||||
return false;
|
||||
|
||||
if (!env.mappedGenerics.find(superTy) && env.containsMappedType(superTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
if (!env.mappedGenerics_DEPRECATED.find(subTy) && env.containsMappedType(subTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
|
||||
env.mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||
env.mappedGenerics_DEPRECATED[subTy].upperBound.insert(superTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get<GenericType>(superTy))
|
||||
return false;
|
||||
|
||||
if (!env.mappedGenerics_DEPRECATED.find(superTy) && env.containsMappedType(superTy))
|
||||
iceReporter->ice("attempting to modify bounds of a potentially visited generic");
|
||||
|
||||
env.mappedGenerics_DEPRECATED[superTy].lowerBound.insert(subTy);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -2181,7 +2492,10 @@ std::pair<TypeId, ErrorVec> Subtyping::handleTypeFunctionReductionResult(const T
|
|||
ErrorVec errors;
|
||||
if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0)
|
||||
{
|
||||
errors.push_back(TypeError{{}, UninhabitedTypeFunction{function}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(Location{}, UninhabitedTypeFunction{function});
|
||||
else
|
||||
errors.push_back(TypeError{{}, UninhabitedTypeFunction{function}});
|
||||
return {builtinTypes->neverType, errors};
|
||||
}
|
||||
if (result.reducedTypes.contains(function))
|
||||
|
@ -2210,4 +2524,128 @@ SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env,
|
|||
return original;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull<Scope> scope)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance);
|
||||
|
||||
SubtypingResult result{true};
|
||||
|
||||
const auto& [lb, ub] = bounds;
|
||||
|
||||
TypeIds lbTypes;
|
||||
for (TypeId t : lb)
|
||||
{
|
||||
t = follow(t);
|
||||
if (const auto mappedBounds = env.mappedGenerics.find(t))
|
||||
{
|
||||
if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it
|
||||
continue;
|
||||
|
||||
auto& [lowerBound, upperBound] = mappedBounds->back();
|
||||
// We're populating the lower bounds, so we prioritize the upper bounds of a mapped generic
|
||||
if (!upperBound.empty())
|
||||
lbTypes.insert(upperBound.begin(), upperBound.end());
|
||||
else if (!lowerBound.empty())
|
||||
lbTypes.insert(lowerBound.begin(), lowerBound.end());
|
||||
else
|
||||
lbTypes.insert(builtinTypes->unknownType);
|
||||
}
|
||||
else
|
||||
lbTypes.insert(t);
|
||||
}
|
||||
|
||||
TypeIds ubTypes;
|
||||
for (TypeId t : ub)
|
||||
{
|
||||
t = follow(t);
|
||||
if (const auto mappedBounds = env.mappedGenerics.find(t))
|
||||
{
|
||||
if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it
|
||||
continue;
|
||||
|
||||
auto& [lowerBound, upperBound] = mappedBounds->back();
|
||||
// We're populating the upper bounds, so we prioritize the lower bounds of a mapped generic
|
||||
if (!lowerBound.empty())
|
||||
ubTypes.insert(lowerBound.begin(), lowerBound.end());
|
||||
else if (!upperBound.empty())
|
||||
ubTypes.insert(upperBound.begin(), upperBound.end());
|
||||
else
|
||||
ubTypes.insert(builtinTypes->unknownType);
|
||||
}
|
||||
else
|
||||
ubTypes.insert(t);
|
||||
}
|
||||
TypeId lowerBound = makeAggregateType<UnionType>(lbTypes.take(), builtinTypes->neverType);
|
||||
TypeId upperBound = makeAggregateType<IntersectionType>(ubTypes.take(), builtinTypes->unknownType);
|
||||
|
||||
std::shared_ptr<const NormalizedType> nt = normalizer->normalize(upperBound);
|
||||
// we say that the result is true if normalization failed because complex types are likely to be inhabited.
|
||||
NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True;
|
||||
|
||||
if (!nt || res == NormalizationResult::HitLimits)
|
||||
result.normalizationTooComplex = true;
|
||||
else if (res == NormalizationResult::False)
|
||||
{
|
||||
/* If the normalized upper bound we're mapping to a generic is
|
||||
* uninhabited, then we must consider the subtyping relation not to
|
||||
* hold.
|
||||
*
|
||||
* This happens eg in <T>() -> (T, T) <: () -> (string, number)
|
||||
*
|
||||
* T appears in covariant position and would have to be both string
|
||||
* and number at once.
|
||||
*
|
||||
* No actual value is both a string and a number, so the test fails.
|
||||
*
|
||||
* TODO: We'll need to add explanitory context here.
|
||||
*/
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
SubtypingEnvironment boundsEnv;
|
||||
boundsEnv.parent = &env;
|
||||
SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope);
|
||||
boundsResult.reasoning.clear();
|
||||
|
||||
result.andAlso(boundsResult);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Subtyping::maybeUpdateBounds(
|
||||
TypeId here,
|
||||
TypeId there,
|
||||
TypeIds& boundsToUpdate,
|
||||
const TypeIds& firstBoundsToCheck,
|
||||
const TypeIds& secondBoundsToCheck
|
||||
)
|
||||
{
|
||||
bool boundsChanged = false;
|
||||
|
||||
if (!firstBoundsToCheck.empty())
|
||||
{
|
||||
for (const TypeId t : firstBoundsToCheck)
|
||||
{
|
||||
if (t != here) // We don't want to bound a generic by itself, ie A <: A
|
||||
{
|
||||
boundsToUpdate.insert(t);
|
||||
boundsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!boundsChanged && !secondBoundsToCheck.empty())
|
||||
{
|
||||
for (const TypeId t : secondBoundsToCheck)
|
||||
{
|
||||
if (t != here)
|
||||
{
|
||||
boundsToUpdate.insert(t);
|
||||
boundsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!boundsChanged && here != there)
|
||||
boundsToUpdate.insert(there);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Unifier2.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauWriteOnlyPropertyMangling)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -195,20 +193,10 @@ TypeId matchLiteralType(
|
|||
|
||||
Property& prop = it->second;
|
||||
|
||||
if (FFlag::LuauWriteOnlyPropertyMangling)
|
||||
{
|
||||
// If the property is write-only, do nothing.
|
||||
if (prop.isWriteOnly())
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we encounter a duplcate property, we may have already
|
||||
// set it to be read-only. If that's the case, the only thing
|
||||
// that will definitely crash is trying to access a write
|
||||
// only property.
|
||||
LUAU_ASSERT(!prop.isWriteOnly());
|
||||
}
|
||||
// If the property is write-only, do nothing.
|
||||
if (prop.isWriteOnly())
|
||||
continue;
|
||||
|
||||
TypeId propTy = *prop.readTy;
|
||||
|
||||
auto it2 = expectedTableTy->props.find(keyStr);
|
||||
|
|
|
@ -2012,7 +2012,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
|||
}
|
||||
else if constexpr (std::is_same_v<T, HasPropConstraint>)
|
||||
{
|
||||
return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context));
|
||||
std::string s = tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context));
|
||||
if (c.inConditional)
|
||||
s += " (inConditional)";
|
||||
return s;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, HasIndexerConstraint>)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
|||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticVisitType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticSetType)
|
||||
|
|
|
@ -30,13 +30,14 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauIceLess)
|
||||
|
||||
|
@ -1279,6 +1280,15 @@ void TypeChecker2::visit(AstStatTypeAlias* stat)
|
|||
if (!module->astScopes.contains(stat))
|
||||
return;
|
||||
|
||||
if (FFlag::LuauNameConstraintRestrictRecursiveTypes)
|
||||
{
|
||||
if (const Scope* scope = findInnermostScope(stat->location))
|
||||
{
|
||||
if (scope->isInvalidTypeAliasName(stat->name.value))
|
||||
reportError(RecursiveRestraintViolation{}, stat->location);
|
||||
}
|
||||
}
|
||||
|
||||
visitGenerics(stat->generics, stat->genericPacks);
|
||||
visit(stat->type);
|
||||
}
|
||||
|
@ -1402,11 +1412,8 @@ void TypeChecker2::visit(AstExprConstantBool* expr)
|
|||
{
|
||||
if (!r.isSubtype)
|
||||
reportError(TypeMismatch{inferredType, bestType}, expr->location);
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
}
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
reportErrors(r.errors);
|
||||
}
|
||||
}
|
||||
|
@ -1436,11 +1443,8 @@ void TypeChecker2::visit(AstExprConstantString* expr)
|
|||
{
|
||||
if (!r.isSubtype)
|
||||
reportError(TypeMismatch{inferredType, bestType}, expr->location);
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
}
|
||||
for (auto& e : r.errors)
|
||||
e.location = expr->location;
|
||||
reportErrors(r.errors);
|
||||
}
|
||||
}
|
||||
|
@ -1511,11 +1515,10 @@ void TypeChecker2::visitCall(AstExprCall* call)
|
|||
fnTy = follow(*selectedOverloadTy);
|
||||
|
||||
if (!isErrorSuppressing(call->location, *selectedOverloadTy))
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : result.errors)
|
||||
e.location = call->location;
|
||||
}
|
||||
{
|
||||
for (auto& e : result.errors)
|
||||
e.location = call->location;
|
||||
}
|
||||
reportErrors(std::move(result.errors));
|
||||
if (result.normalizationTooComplex)
|
||||
{
|
||||
|
@ -1757,7 +1760,11 @@ void TypeChecker2::visitCall(AstExprCall* call)
|
|||
|
||||
void TypeChecker2::visit(AstExprCall* call)
|
||||
{
|
||||
std::optional<InConditionalContext> flipper;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
flipper.emplace(&typeContext, TypeContext::Default);
|
||||
visit(call->func, ValueContext::RValue);
|
||||
flipper.reset();
|
||||
|
||||
for (AstExpr* arg : call->args)
|
||||
visit(arg, ValueContext::RValue);
|
||||
|
@ -1917,6 +1924,10 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context)
|
|||
|
||||
void TypeChecker2::visit(AstExprFunction* fn)
|
||||
{
|
||||
std::optional<InConditionalContext> flipper;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
flipper.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
auto StackPusher = pushStack(fn);
|
||||
|
||||
visitGenerics(fn->generics, fn->genericPacks);
|
||||
|
@ -2070,6 +2081,10 @@ void TypeChecker2::visit(AstExprFunction* fn)
|
|||
|
||||
void TypeChecker2::visit(AstExprTable* expr)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
for (const AstExprTable::Item& item : expr->items)
|
||||
{
|
||||
if (item.key)
|
||||
|
@ -2080,6 +2095,10 @@ void TypeChecker2::visit(AstExprTable* expr)
|
|||
|
||||
void TypeChecker2::visit(AstExprUnary* expr)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauResetConditionalContextProperly && expr->op != AstExprUnary::Op::Not)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
visit(expr->expr, ValueContext::RValue);
|
||||
|
||||
TypeId operandType = lookupType(expr->expr);
|
||||
|
@ -2171,6 +2190,13 @@ void TypeChecker2::visit(AstExprUnary* expr)
|
|||
|
||||
TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
{
|
||||
if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && expr->op != AstExprBinary::CompareNe)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
}
|
||||
|
||||
visit(expr->left, ValueContext::RValue);
|
||||
visit(expr->right, ValueContext::RValue);
|
||||
|
||||
|
@ -2550,7 +2576,10 @@ void TypeChecker2::visit(AstExprTypeAssertion* expr)
|
|||
|
||||
void TypeChecker2::visit(AstExprIfElse* expr)
|
||||
{
|
||||
// TODO!
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
visit(expr->condition, ValueContext::RValue);
|
||||
visit(expr->trueExpr, ValueContext::RValue);
|
||||
visit(expr->falseExpr, ValueContext::RValue);
|
||||
|
@ -2558,6 +2587,10 @@ void TypeChecker2::visit(AstExprIfElse* expr)
|
|||
|
||||
void TypeChecker2::visit(AstExprInterpString* interpString)
|
||||
{
|
||||
std::optional<InConditionalContext> inContext;
|
||||
if (FFlag::LuauResetConditionalContextProperly)
|
||||
inContext.emplace(&typeContext, TypeContext::Default);
|
||||
|
||||
for (AstExpr* expr : interpString->expressions)
|
||||
visit(expr, ValueContext::RValue);
|
||||
}
|
||||
|
@ -3206,11 +3239,10 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location
|
|||
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
|
||||
|
||||
if (!isErrorSuppressing(location, subTy))
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = location;
|
||||
}
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = location;
|
||||
}
|
||||
reportErrors(std::move(r.errors));
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
|
@ -3227,11 +3259,10 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location
|
|||
SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope);
|
||||
|
||||
if (!isErrorSuppressing(location, subTy))
|
||||
if (FFlag::LuauSubtypingCheckFunctionGenericCounts)
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = location;
|
||||
}
|
||||
{
|
||||
for (auto& e : r.errors)
|
||||
e.location = location;
|
||||
}
|
||||
reportErrors(std::move(r.errors));
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
|
|
|
@ -35,11 +35,6 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAvoidExcessiveTypeCopying)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
#include "Luau/VecDeque.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -169,7 +169,10 @@ TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunction
|
|||
if (get<TypeFunctionInstanceType>(guess))
|
||||
continue;
|
||||
|
||||
results.push_back({local->name.value, guess});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
results.emplace_back(local->name.value, guess);
|
||||
else
|
||||
results.push_back({local->name.value, guess});
|
||||
}
|
||||
|
||||
// Submit a guess for return types
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// used to control the recursion limit of any operations done by user-defined type functions
|
||||
// currently, controls serialization, deserialization, and `type.copy`
|
||||
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -470,12 +471,35 @@ struct SerializedGeneric
|
|||
bool isNamed = false;
|
||||
std::string name;
|
||||
T type = nullptr;
|
||||
|
||||
explicit SerializedGeneric(std::string name)
|
||||
: name(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
SerializedGeneric(bool isNamed, std::string name, T type)
|
||||
: isNamed(isNamed)
|
||||
, name(std::move(name))
|
||||
, type(std::move(type))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct SerializedFunctionScope
|
||||
{
|
||||
size_t oldQueueSize = 0;
|
||||
TypeFunctionFunctionType* function = nullptr;
|
||||
|
||||
explicit SerializedFunctionScope(size_t oldQueueSize)
|
||||
: oldQueueSize(oldQueueSize)
|
||||
{
|
||||
}
|
||||
|
||||
SerializedFunctionScope(size_t oldQueueSize, TypeFunctionFunctionType* function)
|
||||
: oldQueueSize(oldQueueSize)
|
||||
, function(function)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Complete inverse of TypeFunctionSerializer
|
||||
|
@ -901,7 +925,10 @@ private:
|
|||
|
||||
void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1)
|
||||
{
|
||||
functionScopes.push_back({queue.size(), f2});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
functionScopes.emplace_back(queue.size(), f2);
|
||||
else
|
||||
functionScopes.push_back({queue.size(), f2});
|
||||
|
||||
std::set<std::pair<bool, std::string>> genericNames;
|
||||
|
||||
|
@ -923,7 +950,10 @@ private:
|
|||
genericNames.insert(nameKey);
|
||||
|
||||
TypeId mapping = state->ctx->arena->addTV(Type(gty->isNamed ? GenericType{state->ctx->scope.get(), gty->name} : GenericType{}));
|
||||
genericTypes.push_back({gty->isNamed, gty->name, mapping});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
genericTypes.emplace_back(gty->isNamed, gty->name, mapping);
|
||||
else
|
||||
genericTypes.push_back({gty->isNamed, gty->name, mapping});
|
||||
}
|
||||
|
||||
for (auto tp : f2->genericPacks)
|
||||
|
@ -944,7 +974,10 @@ private:
|
|||
|
||||
TypePackId mapping =
|
||||
state->ctx->arena->addTypePack(TypePackVar(gtp->isNamed ? GenericTypePack{state->ctx->scope.get(), gtp->name} : GenericTypePack{}));
|
||||
genericPacks.push_back({gtp->isNamed, gtp->name, mapping});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
genericPacks.emplace_back(gtp->isNamed, gtp->name, mapping);
|
||||
else
|
||||
genericPacks.push_back({gtp->isNamed, gtp->name, mapping});
|
||||
}
|
||||
|
||||
f1->generics.reserve(f2->generics.size());
|
||||
|
|
|
@ -157,4 +157,10 @@ std::vector<TypeId> TypeIds::take()
|
|||
return std::move(order);
|
||||
}
|
||||
|
||||
void TypeIds::reserve(size_t n)
|
||||
{
|
||||
order.reserve(n);
|
||||
}
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/TopoSortStatements.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
@ -33,6 +32,8 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1852,6 +1853,16 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti
|
|||
for (const auto& el : global.paramNames)
|
||||
ftv->argNames.push_back(FunctionArgument{el.first.value, el.second});
|
||||
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = global.getAttribute(AstAttr::Type::Deprecated);
|
||||
ftv->isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
ftv->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
|
||||
Name fnName(global.name.value);
|
||||
|
||||
currentModule->declaredGlobals[fnName] = fnType;
|
||||
|
@ -3930,6 +3941,16 @@ std::pair<TypeId, ScopePtr> TypeChecker::checkFunctionSignature(
|
|||
for (AstLocal* local : expr.args)
|
||||
ftv->argNames.push_back(FunctionArgument{local->name.value, local->location});
|
||||
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = expr.getAttribute(AstAttr::Type::Deprecated);
|
||||
ftv->isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
ftv->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(funTy, funScope);
|
||||
}
|
||||
|
||||
|
@ -5768,6 +5789,16 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno
|
|||
ftv->argNames.push_back(std::nullopt);
|
||||
}
|
||||
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated);
|
||||
ftv->isDeprecatedFunction = deprecatedAttr != nullptr;
|
||||
if (deprecatedAttr)
|
||||
{
|
||||
ftv->deprecatedInfo = std::make_shared<AstAttr::DeprecatedInfo>(deprecatedAttr->deprecatedInfo());
|
||||
}
|
||||
}
|
||||
|
||||
return fnType;
|
||||
}
|
||||
else if (auto typeOf = annotation.as<AstTypeTypeof>())
|
||||
|
@ -5913,7 +5944,10 @@ TypeId TypeChecker::instantiateTypeFun(
|
|||
}
|
||||
if (applyTypeFunction.encounteredForwardedType)
|
||||
{
|
||||
reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}});
|
||||
if (FFlag::LuauNameConstraintRestrictRecursiveTypes)
|
||||
reportError(TypeError{location, RecursiveRestraintViolation{}});
|
||||
else
|
||||
reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}});
|
||||
return errorRecoveryType(scope);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
// Maximum number of steps to follow when traversing a path. May not always
|
||||
// equate to the number of components in a path, depending on the traversal
|
||||
|
@ -168,85 +169,127 @@ Path PathBuilder::build()
|
|||
|
||||
PathBuilder& PathBuilder::readProp(std::string name)
|
||||
{
|
||||
components.push_back(Property{std::move(name), true});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(Property{std::move(name), true});
|
||||
else
|
||||
components.push_back(Property{std::move(name), true});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::writeProp(std::string name)
|
||||
{
|
||||
components.push_back(Property{std::move(name), false});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(Property{std::move(name), false});
|
||||
else
|
||||
components.push_back(Property{std::move(name), false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::prop(std::string name)
|
||||
{
|
||||
components.push_back(Property{std::move(name)});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(Property{std::move(name)});
|
||||
else
|
||||
components.push_back(Property{std::move(name)});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::index(size_t i)
|
||||
{
|
||||
components.push_back(Index{i});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(Index{i});
|
||||
else
|
||||
components.push_back(Index{i});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::mt()
|
||||
{
|
||||
components.push_back(TypeField::Metatable);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::Metatable);
|
||||
else
|
||||
components.push_back(TypeField::Metatable);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::lb()
|
||||
{
|
||||
components.push_back(TypeField::LowerBound);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::LowerBound);
|
||||
else
|
||||
components.push_back(TypeField::LowerBound);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::ub()
|
||||
{
|
||||
components.push_back(TypeField::UpperBound);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::UpperBound);
|
||||
else
|
||||
components.push_back(TypeField::UpperBound);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::indexKey()
|
||||
{
|
||||
components.push_back(TypeField::IndexLookup);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::IndexLookup);
|
||||
else
|
||||
components.push_back(TypeField::IndexLookup);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::indexValue()
|
||||
{
|
||||
components.push_back(TypeField::IndexResult);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::IndexResult);
|
||||
else
|
||||
components.push_back(TypeField::IndexResult);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::negated()
|
||||
{
|
||||
components.push_back(TypeField::Negated);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::Negated);
|
||||
else
|
||||
components.push_back(TypeField::Negated);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::variadic()
|
||||
{
|
||||
components.push_back(TypeField::Variadic);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(TypeField::Variadic);
|
||||
else
|
||||
components.push_back(TypeField::Variadic);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::args()
|
||||
{
|
||||
components.push_back(PackField::Arguments);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(PackField::Arguments);
|
||||
else
|
||||
components.push_back(PackField::Arguments);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::rets()
|
||||
{
|
||||
components.push_back(PackField::Returns);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(PackField::Returns);
|
||||
else
|
||||
components.push_back(PackField::Returns);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::tail()
|
||||
{
|
||||
components.push_back(PackField::Tail);
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
components.emplace_back(PackField::Tail);
|
||||
else
|
||||
components.push_back(PackField::Tail);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -96,6 +98,8 @@ std::optional<Property> findTableProperty(NotNull<BuiltinTypes> builtinTypes, Er
|
|||
}
|
||||
else if (get<AnyType>(index))
|
||||
return builtinTypes->anyType;
|
||||
else if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(location, GenericError{"__index should either be a function or table. Got " + toString(index)});
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||
|
||||
|
@ -127,14 +131,17 @@ std::optional<TypeId> findMetatableEntry(
|
|||
const TableType* mtt = getTableType(unwrapped);
|
||||
if (!mtt)
|
||||
{
|
||||
errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}});
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(location, GenericError{"Metatable was not a table"});
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}});
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto it = mtt->props.find(entry);
|
||||
if (it != mtt->props.end())
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2)
|
||||
{
|
||||
if (it->second.readTy)
|
||||
return it->second.readTy;
|
||||
|
@ -176,7 +183,7 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
|
|||
const auto& it = tableType->props.find(name);
|
||||
if (it != tableType->props.end())
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2)
|
||||
{
|
||||
switch (context)
|
||||
{
|
||||
|
@ -231,6 +238,8 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
|
|||
}
|
||||
else if (get<AnyType>(index))
|
||||
return builtinTypes->anyType;
|
||||
else if (FFlag::LuauEmplaceNotPushBack)
|
||||
errors.emplace_back(location, GenericError{"__index should either be a function or table. Got " + toString(index)});
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||
|
||||
|
@ -338,10 +347,10 @@ TypePack extendTypePack(
|
|||
TypePack newPack;
|
||||
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
if (FFlag::LuauTidyTypeUtils)
|
||||
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2)
|
||||
result.tail = newPack.tail;
|
||||
size_t overridesIndex = 0;
|
||||
while (result.head.size() < length)
|
||||
|
@ -353,7 +362,7 @@ TypePack extendTypePack(
|
|||
}
|
||||
else
|
||||
{
|
||||
if (FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2)
|
||||
{
|
||||
FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity};
|
||||
t = arena.addType(ft);
|
||||
|
@ -760,6 +769,131 @@ bool isApproximatelyTruthyType(TypeId ty)
|
|||
return false;
|
||||
}
|
||||
|
||||
UnionBuilder::UnionBuilder(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
{
|
||||
}
|
||||
|
||||
void UnionBuilder::add(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (is<NeverType>(ty) || isTop)
|
||||
return;
|
||||
|
||||
if (is<UnknownType>(ty))
|
||||
{
|
||||
isTop = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto utv = get<UnionType>(ty))
|
||||
{
|
||||
for (auto option : utv)
|
||||
options.insert(option);
|
||||
}
|
||||
else
|
||||
options.insert(ty);
|
||||
}
|
||||
|
||||
TypeId UnionBuilder::build()
|
||||
{
|
||||
if (isTop)
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (options.empty())
|
||||
return builtinTypes->neverType;
|
||||
|
||||
if (options.size() == 1)
|
||||
return options.front();
|
||||
|
||||
return arena->addType(UnionType{options.take()});
|
||||
}
|
||||
|
||||
size_t UnionBuilder::size() const
|
||||
{
|
||||
return options.size();
|
||||
}
|
||||
|
||||
void UnionBuilder::reserve(size_t size)
|
||||
{
|
||||
options.reserve(size);
|
||||
}
|
||||
|
||||
IntersectionBuilder::IntersectionBuilder(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
{
|
||||
}
|
||||
|
||||
void IntersectionBuilder::add(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
|
||||
if (is<NeverType>(ty))
|
||||
{
|
||||
isBottom = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (is<UnknownType>(ty))
|
||||
return;
|
||||
|
||||
if (auto itv = get<IntersectionType>(ty))
|
||||
{
|
||||
for (auto part : itv)
|
||||
parts.insert(part);
|
||||
}
|
||||
else
|
||||
parts.insert(ty);
|
||||
}
|
||||
|
||||
TypeId IntersectionBuilder::build()
|
||||
{
|
||||
if (isBottom)
|
||||
return builtinTypes->neverType;
|
||||
|
||||
if (parts.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (parts.size() == 1)
|
||||
return parts.front();
|
||||
|
||||
return arena->addType(IntersectionType{parts.take()});
|
||||
}
|
||||
|
||||
size_t IntersectionBuilder::size() const
|
||||
{
|
||||
return parts.size();
|
||||
}
|
||||
|
||||
void IntersectionBuilder::reserve(size_t size)
|
||||
{
|
||||
parts.reserve(size);
|
||||
}
|
||||
|
||||
|
||||
TypeId addIntersection(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, std::initializer_list<TypeId> list)
|
||||
{
|
||||
IntersectionBuilder ib(arena, builtinTypes);
|
||||
ib.reserve(list.size());
|
||||
for (TypeId part : list)
|
||||
ib.add(part);
|
||||
|
||||
return ib.build();
|
||||
}
|
||||
|
||||
TypeId addUnion(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, std::initializer_list<TypeId> list)
|
||||
{
|
||||
UnionBuilder ub(arena, builtinTypes);
|
||||
ub.reserve(list.size());
|
||||
for (TypeId option : list)
|
||||
ub.add(option);
|
||||
return ub.build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Luau
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauEmplaceNotPushBack)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLimitUnification)
|
||||
LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -131,23 +135,43 @@ Unifier2::Unifier2(
|
|||
{
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
UnifyResult Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
iterationCount = 0;
|
||||
return unify_(subTy, superTy);
|
||||
}
|
||||
|
||||
UnifyResult Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
iterationCount = 0;
|
||||
return unify_(subTp, superTp);
|
||||
}
|
||||
|
||||
UnifyResult Unifier2::unify_(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
if (FFlag::LuauLimitUnification)
|
||||
{
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && iterationCount >= FInt::LuauTypeInferIterationLimit)
|
||||
return UnifyResult::TooComplex;
|
||||
|
||||
++iterationCount;
|
||||
}
|
||||
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
if (auto subGen = genericSubstitutions.find(subTy))
|
||||
return unify(*subGen, superTy);
|
||||
return unify_(*subGen, superTy);
|
||||
|
||||
if (auto superGen = genericSubstitutions.find(superTy))
|
||||
return unify(subTy, *superGen);
|
||||
return unify_(subTy, *superGen);
|
||||
|
||||
if (seenTypePairings.contains({subTy, superTy}))
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
seenTypePairings.insert({subTy, superTy});
|
||||
|
||||
if (subTy == superTy)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
// We have potentially done some unifications while dispatching either `SubtypeConstraint` or `PackSubtypeConstraint`,
|
||||
// so rather than implementing backtracking or traversing the entire type graph multiple times, we could push
|
||||
|
@ -159,10 +183,13 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy))
|
||||
{
|
||||
if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTy) || uninhabitedTypeFunctions->contains(superTy)))
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
|
||||
return true;
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
incompleteSubtypes.emplace_back(SubtypeConstraint{subTy, superTy});
|
||||
else
|
||||
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
FreeType* subFree = getMutable<FreeType>(subTy);
|
||||
|
@ -179,44 +206,44 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
}
|
||||
|
||||
if (subFree || superFree)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
auto subFn = get<FunctionType>(subTy);
|
||||
auto superFn = get<FunctionType>(superTy);
|
||||
if (subFn && superFn)
|
||||
return unify(subTy, superFn);
|
||||
return unify_(subTy, superFn);
|
||||
|
||||
auto subUnion = get<UnionType>(subTy);
|
||||
auto superUnion = get<UnionType>(superTy);
|
||||
if (subUnion)
|
||||
return unify(subUnion, superTy);
|
||||
return unify_(subUnion, superTy);
|
||||
else if (superUnion)
|
||||
return unify(subTy, superUnion);
|
||||
return unify_(subTy, superUnion);
|
||||
|
||||
auto subIntersection = get<IntersectionType>(subTy);
|
||||
auto superIntersection = get<IntersectionType>(superTy);
|
||||
if (subIntersection)
|
||||
return unify(subIntersection, superTy);
|
||||
return unify_(subIntersection, superTy);
|
||||
else if (superIntersection)
|
||||
return unify(subTy, superIntersection);
|
||||
return unify_(subTy, superIntersection);
|
||||
|
||||
auto subNever = get<NeverType>(subTy);
|
||||
auto superNever = get<NeverType>(superTy);
|
||||
if (subNever && superNever)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
else if (subNever && superFn)
|
||||
{
|
||||
// If `never` is the subtype, then we can propagate that inward.
|
||||
bool argResult = unify(superFn->argTypes, builtinTypes->neverTypePack);
|
||||
bool retResult = unify(builtinTypes->neverTypePack, superFn->retTypes);
|
||||
return argResult && retResult;
|
||||
UnifyResult argResult = unify_(superFn->argTypes, builtinTypes->neverTypePack);
|
||||
UnifyResult retResult = unify_(builtinTypes->neverTypePack, superFn->retTypes);
|
||||
return argResult & retResult;
|
||||
}
|
||||
else if (subFn && superNever)
|
||||
{
|
||||
// If `never` is the supertype, then we can propagate that inward.
|
||||
bool argResult = unify(builtinTypes->neverTypePack, subFn->argTypes);
|
||||
bool retResult = unify(subFn->retTypes, builtinTypes->neverTypePack);
|
||||
return argResult && retResult;
|
||||
UnifyResult argResult = unify_(builtinTypes->neverTypePack, subFn->argTypes);
|
||||
UnifyResult retResult = unify_(subFn->retTypes, builtinTypes->neverTypePack);
|
||||
return argResult & retResult;
|
||||
}
|
||||
|
||||
auto subAny = get<AnyType>(subTy);
|
||||
|
@ -226,15 +253,15 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
auto superTable = get<TableType>(superTy);
|
||||
|
||||
if (subAny && superAny)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
else if (subAny && superFn)
|
||||
return unify(subAny, superFn);
|
||||
return unify_(subAny, superFn);
|
||||
else if (subFn && superAny)
|
||||
return unify(subFn, superAny);
|
||||
return unify_(subFn, superAny);
|
||||
else if (subAny && superTable)
|
||||
return unify(subAny, superTable);
|
||||
return unify_(subAny, superTable);
|
||||
else if (subTable && superAny)
|
||||
return unify(subTable, superAny);
|
||||
return unify_(subTable, superAny);
|
||||
|
||||
if (subTable && superTable)
|
||||
{
|
||||
|
@ -245,35 +272,35 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
|||
LUAU_ASSERT(!subTable->boundTo);
|
||||
LUAU_ASSERT(!superTable->boundTo);
|
||||
|
||||
return unify(subTable, superTable);
|
||||
return unify_(subTable, superTable);
|
||||
}
|
||||
|
||||
auto subMetatable = get<MetatableType>(subTy);
|
||||
auto superMetatable = get<MetatableType>(superTy);
|
||||
if (subMetatable && superMetatable)
|
||||
return unify(subMetatable, superMetatable);
|
||||
return unify_(subMetatable, superMetatable);
|
||||
else if (subMetatable && superAny)
|
||||
return unify(subMetatable, superAny);
|
||||
return unify_(subMetatable, superAny);
|
||||
else if (subAny && superMetatable)
|
||||
return unify(subAny, superMetatable);
|
||||
return unify_(subAny, superMetatable);
|
||||
else if (subMetatable) // if we only have one metatable, unify with the inner table
|
||||
return unify(subMetatable->table, superTy);
|
||||
return unify_(subMetatable->table, superTy);
|
||||
else if (superMetatable) // if we only have one metatable, unify with the inner table
|
||||
return unify(subTy, superMetatable->table);
|
||||
return unify_(subTy, superMetatable->table);
|
||||
|
||||
auto [subNegation, superNegation] = get2<NegationType, NegationType>(subTy, superTy);
|
||||
if (subNegation && superNegation)
|
||||
return unify(subNegation->ty, superNegation->ty);
|
||||
return unify_(subNegation->ty, superNegation->ty);
|
||||
|
||||
// The unification failed, but we're not doing type checking.
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
// If superTy is a function and subTy already has a
|
||||
// potentially-compatible function in its upper bound, we assume that
|
||||
// the function is not overloaded and attempt to combine superTy into
|
||||
// subTy's existing function bound.
|
||||
bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
||||
UnifyResult Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
FreeType* subFree = getMutable<FreeType>(subTy);
|
||||
LUAU_ASSERT(subFree);
|
||||
|
@ -282,13 +309,13 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
|||
{
|
||||
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
|
||||
expandedFreeTypes[subTy].push_back(superTy);
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
};
|
||||
|
||||
TypeId upperBound = follow(subFree->upperBound);
|
||||
|
||||
if (get<FunctionType>(upperBound))
|
||||
return unify(subFree->upperBound, superTy);
|
||||
return unify_(subFree->upperBound, superTy);
|
||||
|
||||
const FunctionType* superFunction = get<FunctionType>(superTy);
|
||||
if (!superFunction)
|
||||
|
@ -302,7 +329,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
|||
if (!upperBoundIntersection)
|
||||
return doDefault();
|
||||
|
||||
bool ok = true;
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
bool foundOne = false;
|
||||
|
||||
for (TypeId part : upperBoundIntersection->parts)
|
||||
|
@ -316,17 +343,17 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
|
|||
if (!subArgTail && subArgHead.size() == superArgHead.size())
|
||||
{
|
||||
foundOne = true;
|
||||
ok &= unify(part, superTy);
|
||||
result &= unify_(part, superTy);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundOne)
|
||||
return ok;
|
||||
return result;
|
||||
else
|
||||
return doDefault();
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
||||
UnifyResult Unifier2::unify_(TypeId subTy, const FunctionType* superFn)
|
||||
{
|
||||
const FunctionType* subFn = get<FunctionType>(subTy);
|
||||
|
||||
|
@ -359,64 +386,86 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
|||
}
|
||||
}
|
||||
|
||||
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
||||
bool retResult = unify(subFn->retTypes, superFn->retTypes);
|
||||
return argResult && retResult;
|
||||
UnifyResult argResult = unify_(superFn->argTypes, subFn->argTypes);
|
||||
UnifyResult retResult = unify_(subFn->retTypes, superFn->retTypes);
|
||||
return argResult & retResult;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const UnionType* subUnion, TypeId superTy)
|
||||
UnifyResult Unifier2::unify_(const UnionType* subUnion, TypeId superTy)
|
||||
{
|
||||
bool result = true;
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
|
||||
// if the occurs check fails for any option, it fails overall
|
||||
for (auto subOption : subUnion->options)
|
||||
{
|
||||
if (areCompatible(subOption, superTy))
|
||||
result &= unify(subOption, superTy);
|
||||
result &= unify_(subOption, superTy);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, const UnionType* superUnion)
|
||||
UnifyResult Unifier2::unify_(TypeId subTy, const UnionType* superUnion)
|
||||
{
|
||||
bool result = true;
|
||||
if (FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions)
|
||||
{
|
||||
subTy = follow(subTy);
|
||||
// T <: T | U1 | U2 | ... | Un is trivially true, so we don't gain any information by unifying
|
||||
for (const auto superOption : superUnion)
|
||||
{
|
||||
if (subTy == superOption)
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
|
||||
// if the occurs check fails for any option, it fails overall
|
||||
for (auto superOption : superUnion->options)
|
||||
{
|
||||
if (areCompatible(subTy, superOption))
|
||||
result &= unify(subTy, superOption);
|
||||
result &= unify_(subTy, superOption);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const IntersectionType* subIntersection, TypeId superTy)
|
||||
UnifyResult Unifier2::unify_(const IntersectionType* subIntersection, TypeId superTy)
|
||||
{
|
||||
bool result = true;
|
||||
if (FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions)
|
||||
{
|
||||
superTy = follow(superTy);
|
||||
// T & I1 & I2 & ... & In <: T is trivially true, so we don't gain any information by unifying
|
||||
for (const auto subOption : subIntersection)
|
||||
{
|
||||
if (superTy == subOption)
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
|
||||
// if the occurs check fails for any part, it fails overall
|
||||
for (auto subPart : subIntersection->parts)
|
||||
result &= unify(subPart, superTy);
|
||||
result &= unify_(subPart, superTy);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TypeId subTy, const IntersectionType* superIntersection)
|
||||
UnifyResult Unifier2::unify_(TypeId subTy, const IntersectionType* superIntersection)
|
||||
{
|
||||
bool result = true;
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
|
||||
// if the occurs check fails for any part, it fails overall
|
||||
for (auto superPart : superIntersection->parts)
|
||||
result &= unify(subTy, superPart);
|
||||
result &= unify_(subTy, superPart);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
||||
UnifyResult Unifier2::unify_(TableType* subTable, const TableType* superTable)
|
||||
{
|
||||
bool result = true;
|
||||
UnifyResult result = UnifyResult::Ok;
|
||||
|
||||
// It suffices to only check one direction of properties since we'll only ever have work to do during unification
|
||||
// if the property is present in both table types.
|
||||
|
@ -429,10 +478,10 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
const Property& superProp = superPropOpt->second;
|
||||
|
||||
if (subProp.readTy && superProp.readTy)
|
||||
result &= unify(*subProp.readTy, *superProp.readTy);
|
||||
result &= unify_(*subProp.readTy, *superProp.readTy);
|
||||
|
||||
if (subProp.writeTy && superProp.writeTy)
|
||||
result &= unify(*superProp.writeTy, *subProp.writeTy);
|
||||
result &= unify_(*superProp.writeTy, *subProp.writeTy);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,7 +490,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
|
||||
while (subTypeParamsIter != subTable->instantiatedTypeParams.end() && superTypeParamsIter != superTable->instantiatedTypeParams.end())
|
||||
{
|
||||
result &= unify(*subTypeParamsIter, *superTypeParamsIter);
|
||||
result &= unify_(*subTypeParamsIter, *superTypeParamsIter);
|
||||
|
||||
subTypeParamsIter++;
|
||||
superTypeParamsIter++;
|
||||
|
@ -453,7 +502,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
while (subTypePackParamsIter != subTable->instantiatedTypePackParams.end() &&
|
||||
superTypePackParamsIter != superTable->instantiatedTypePackParams.end())
|
||||
{
|
||||
result &= unify(*subTypePackParamsIter, *superTypePackParamsIter);
|
||||
result &= unify_(*subTypePackParamsIter, *superTypePackParamsIter);
|
||||
|
||||
subTypePackParamsIter++;
|
||||
superTypePackParamsIter++;
|
||||
|
@ -461,13 +510,13 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
|
||||
if (subTable->indexer && superTable->indexer)
|
||||
{
|
||||
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
||||
result &= unify_(subTable->indexer->indexType, superTable->indexer->indexType);
|
||||
result &= unify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
|
||||
if (FFlag::LuauEagerGeneralization4)
|
||||
{
|
||||
// FIXME: We can probably do something more efficient here.
|
||||
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);
|
||||
result &= unify(superTable->indexer->indexResultType, subTable->indexer->indexResultType);
|
||||
result &= unify_(superTable->indexer->indexType, subTable->indexer->indexType);
|
||||
result &= unify_(superTable->indexer->indexResultType, subTable->indexer->indexResultType);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,104 +547,126 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const MetatableType* subMetatable, const MetatableType* superMetatable)
|
||||
UnifyResult Unifier2::unify_(const MetatableType* subMetatable, const MetatableType* superMetatable)
|
||||
{
|
||||
return unify(subMetatable->metatable, superMetatable->metatable) && unify(subMetatable->table, superMetatable->table);
|
||||
UnifyResult metatableResult = unify_(subMetatable->metatable, superMetatable->metatable);
|
||||
if (metatableResult != UnifyResult::Ok)
|
||||
return metatableResult;
|
||||
return unify_(subMetatable->table, superMetatable->table);
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const AnyType* subAny, const FunctionType* superFn)
|
||||
UnifyResult Unifier2::unify_(const AnyType* subAny, const FunctionType* superFn)
|
||||
{
|
||||
// If `any` is the subtype, then we can propagate that inward.
|
||||
bool argResult = unify(superFn->argTypes, builtinTypes->anyTypePack);
|
||||
bool retResult = unify(builtinTypes->anyTypePack, superFn->retTypes);
|
||||
return argResult && retResult;
|
||||
UnifyResult argResult = unify_(superFn->argTypes, builtinTypes->anyTypePack);
|
||||
UnifyResult retResult = unify_(builtinTypes->anyTypePack, superFn->retTypes);
|
||||
return argResult & retResult;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const FunctionType* subFn, const AnyType* superAny)
|
||||
UnifyResult Unifier2::unify_(const FunctionType* subFn, const AnyType* superAny)
|
||||
{
|
||||
// If `any` is the supertype, then we can propagate that inward.
|
||||
bool argResult = unify(builtinTypes->anyTypePack, subFn->argTypes);
|
||||
bool retResult = unify(subFn->retTypes, builtinTypes->anyTypePack);
|
||||
return argResult && retResult;
|
||||
UnifyResult argResult = unify_(builtinTypes->anyTypePack, subFn->argTypes);
|
||||
UnifyResult retResult = unify_(subFn->retTypes, builtinTypes->anyTypePack);
|
||||
return argResult & retResult;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const AnyType* subAny, const TableType* superTable)
|
||||
UnifyResult Unifier2::unify_(const AnyType* subAny, const TableType* superTable)
|
||||
{
|
||||
for (const auto& [propName, prop] : superTable->props)
|
||||
{
|
||||
if (prop.readTy)
|
||||
unify(builtinTypes->anyType, *prop.readTy);
|
||||
unify_(builtinTypes->anyType, *prop.readTy);
|
||||
|
||||
if (prop.writeTy)
|
||||
unify(*prop.writeTy, builtinTypes->anyType);
|
||||
unify_(*prop.writeTy, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
if (superTable->indexer)
|
||||
{
|
||||
unify(builtinTypes->anyType, superTable->indexer->indexType);
|
||||
unify(builtinTypes->anyType, superTable->indexer->indexResultType);
|
||||
unify_(builtinTypes->anyType, superTable->indexer->indexType);
|
||||
unify_(builtinTypes->anyType, superTable->indexer->indexResultType);
|
||||
}
|
||||
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const TableType* subTable, const AnyType* superAny)
|
||||
UnifyResult Unifier2::unify_(const TableType* subTable, const AnyType* superAny)
|
||||
{
|
||||
for (const auto& [propName, prop] : subTable->props)
|
||||
{
|
||||
if (prop.readTy)
|
||||
unify(*prop.readTy, builtinTypes->anyType);
|
||||
unify_(*prop.readTy, builtinTypes->anyType);
|
||||
|
||||
if (prop.writeTy)
|
||||
unify(builtinTypes->anyType, *prop.writeTy);
|
||||
unify_(builtinTypes->anyType, *prop.writeTy);
|
||||
}
|
||||
|
||||
if (subTable->indexer)
|
||||
{
|
||||
unify(subTable->indexer->indexType, builtinTypes->anyType);
|
||||
unify(subTable->indexer->indexResultType, builtinTypes->anyType);
|
||||
unify_(subTable->indexer->indexType, builtinTypes->anyType);
|
||||
unify_(subTable->indexer->indexResultType, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const MetatableType* subMetatable, const AnyType*)
|
||||
UnifyResult Unifier2::unify_(const MetatableType* subMetatable, const AnyType*)
|
||||
{
|
||||
return unify(subMetatable->metatable, builtinTypes->anyType) && unify(subMetatable->table, builtinTypes->anyType);
|
||||
UnifyResult metatableResult = unify_(subMetatable->metatable, builtinTypes->anyType);
|
||||
if (metatableResult != UnifyResult::Ok)
|
||||
return metatableResult;
|
||||
|
||||
return unify_(subMetatable->table, builtinTypes->anyType);
|
||||
}
|
||||
|
||||
bool Unifier2::unify(const AnyType*, const MetatableType* superMetatable)
|
||||
UnifyResult Unifier2::unify_(const AnyType*, const MetatableType* superMetatable)
|
||||
{
|
||||
return unify(builtinTypes->anyType, superMetatable->metatable) && unify(builtinTypes->anyType, superMetatable->table);
|
||||
UnifyResult metatableResult = unify_(builtinTypes->anyType, superMetatable->metatable);
|
||||
if (metatableResult != UnifyResult::Ok)
|
||||
return metatableResult;
|
||||
|
||||
return unify_(builtinTypes->anyType, superMetatable->table);
|
||||
}
|
||||
|
||||
// FIXME? This should probably return an ErrorVec or an optional<TypeError>
|
||||
// rather than a boolean to signal an occurs check failure.
|
||||
bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
UnifyResult Unifier2::unify_(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
if (FFlag::LuauLimitUnification)
|
||||
{
|
||||
if (FInt::LuauTypeInferIterationLimit > 0 && iterationCount >= FInt::LuauTypeInferIterationLimit)
|
||||
return UnifyResult::TooComplex;
|
||||
|
||||
++iterationCount;
|
||||
}
|
||||
|
||||
subTp = follow(subTp);
|
||||
superTp = follow(superTp);
|
||||
|
||||
if (auto subGen = genericPackSubstitutions.find(subTp))
|
||||
return unify(*subGen, superTp);
|
||||
return unify_(*subGen, superTp);
|
||||
|
||||
if (auto superGen = genericPackSubstitutions.find(superTp))
|
||||
return unify(subTp, *superGen);
|
||||
return unify_(subTp, *superGen);
|
||||
|
||||
if (seenTypePackPairings.contains({subTp, superTp}))
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
seenTypePackPairings.insert({subTp, superTp});
|
||||
|
||||
if (subTp == superTp)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
if (isIrresolvable(subTp) || isIrresolvable(superTp))
|
||||
{
|
||||
if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTp) || uninhabitedTypeFunctions->contains(superTp)))
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
|
||||
return true;
|
||||
if (FFlag::LuauEmplaceNotPushBack)
|
||||
incompleteSubtypes.emplace_back(PackSubtypeConstraint{subTp, superTp});
|
||||
else
|
||||
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
||||
|
@ -607,11 +678,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||
if (OccursCheckResult::Fail == occursCheck(seen, subTp, superTp))
|
||||
{
|
||||
emplaceTypePack<BoundTypePack>(asMutable(subTp), builtinTypes->errorTypePack);
|
||||
return false;
|
||||
return UnifyResult::OccursCheckFailed;
|
||||
}
|
||||
|
||||
emplaceTypePack<BoundTypePack>(asMutable(subTp), superTp);
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
if (superFree)
|
||||
|
@ -620,11 +691,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||
if (OccursCheckResult::Fail == occursCheck(seen, superTp, subTp))
|
||||
{
|
||||
emplaceTypePack<BoundTypePack>(asMutable(superTp), builtinTypes->errorTypePack);
|
||||
return false;
|
||||
return UnifyResult::OccursCheckFailed;
|
||||
}
|
||||
|
||||
emplaceTypePack<BoundTypePack>(asMutable(superTp), subTp);
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size());
|
||||
|
@ -640,10 +711,10 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||
}
|
||||
|
||||
if (subTypes.size() < maxLength || superTypes.size() < maxLength)
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
|
||||
for (size_t i = 0; i < maxLength; ++i)
|
||||
unify(subTypes[i], superTypes[i]);
|
||||
unify_(subTypes[i], superTypes[i]);
|
||||
|
||||
if (subTail && superTail)
|
||||
{
|
||||
|
@ -651,7 +722,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||
TypePackId followedSuperTail = follow(*superTail);
|
||||
|
||||
if (get<FreeTypePack>(followedSubTail) || get<FreeTypePack>(followedSuperTail))
|
||||
return unify(followedSubTail, followedSuperTail);
|
||||
return unify_(followedSubTail, followedSuperTail);
|
||||
}
|
||||
else if (subTail)
|
||||
{
|
||||
|
@ -666,7 +737,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
|||
emplaceTypePack<BoundTypePack>(asMutable(followedSuperTail), builtinTypes->emptyTypePack);
|
||||
}
|
||||
|
||||
return true;
|
||||
return UnifyResult::Ok;
|
||||
}
|
||||
|
||||
TypeId Unifier2::mkUnion(TypeId left, TypeId right)
|
||||
|
|
|
@ -197,7 +197,14 @@ public:
|
|||
Deprecated,
|
||||
};
|
||||
|
||||
AstAttr(const Location& location, Type type);
|
||||
struct DeprecatedInfo
|
||||
{
|
||||
bool deprecated = false;
|
||||
std::optional<std::string> use;
|
||||
std::optional<std::string> reason;
|
||||
};
|
||||
|
||||
AstAttr(const Location& location, Type type, AstArray<AstExpr*> args);
|
||||
|
||||
AstAttr* asAttr() override
|
||||
{
|
||||
|
@ -206,7 +213,10 @@ public:
|
|||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
DeprecatedInfo deprecatedInfo() const;
|
||||
|
||||
Type type;
|
||||
AstArray<AstExpr*> args;
|
||||
};
|
||||
|
||||
class AstExpr : public AstNode
|
||||
|
@ -455,6 +465,7 @@ public:
|
|||
|
||||
bool hasNativeAttribute() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
AstAttr* getAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -499,6 +510,8 @@ public:
|
|||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
std::optional<AstExpr*> getRecord(const char* key) const;
|
||||
|
||||
AstArray<Item> items;
|
||||
};
|
||||
|
||||
|
@ -960,6 +973,7 @@ public:
|
|||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
AstAttr* getAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstName name;
|
||||
|
@ -1117,6 +1131,7 @@ public:
|
|||
|
||||
bool isCheckedFunction() const;
|
||||
bool hasAttribute(AstAttr::Type attributeType) const;
|
||||
AstAttr* getAttribute(AstAttr::Type attributeType) const;
|
||||
|
||||
AstArray<AstAttr*> attributes;
|
||||
AstArray<AstGenericType*> generics;
|
||||
|
@ -1561,6 +1576,8 @@ public:
|
|||
};
|
||||
|
||||
bool isLValue(const AstExpr*);
|
||||
bool isConstantLiteral(const AstExpr*);
|
||||
bool isLiteralTable(const AstExpr*);
|
||||
AstName getIdentifier(AstExpr*);
|
||||
Location getLocation(const AstTypeList& typeList);
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ struct Lexeme
|
|||
BlockComment,
|
||||
|
||||
Attribute,
|
||||
AttributeOpen,
|
||||
|
||||
BrokenString,
|
||||
BrokenComment,
|
||||
|
|
|
@ -130,7 +130,13 @@ private:
|
|||
// function funcname funcbody
|
||||
LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray<AstAttr*>& attributes = {nullptr, 0});
|
||||
|
||||
std::optional<AstAttr::Type> validateAttribute(const char* attributeName, const TempVector<AstAttr*>& attributes);
|
||||
std::optional<AstAttr::Type> validateAttribute(
|
||||
Location loc,
|
||||
const char* attributeName,
|
||||
const TempVector<AstAttr*>& attributes,
|
||||
const AstArray<AstExpr*>& args
|
||||
);
|
||||
std::optional<AstAttr::Type> validateAttribute_DEPRECATED(const char* attributeName, const TempVector<AstAttr*>& attributes);
|
||||
|
||||
// attribute ::= '@' NAME
|
||||
void parseAttribute(TempVector<AstAttr*>& attribute);
|
||||
|
@ -287,6 +293,7 @@ private:
|
|||
// simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | [attributes] FUNCTION body | primaryexp
|
||||
AstExpr* parseSimpleExpr();
|
||||
|
||||
std::tuple<AstArray<AstExpr*>, Location, Location> parseCallList(TempVector<Position>* commaPositions);
|
||||
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||
AstExpr* parseFunctionArgs(AstExpr* func, bool self);
|
||||
|
||||
|
|
109
Ast/src/Ast.cpp
109
Ast/src/Ast.cpp
|
@ -2,19 +2,40 @@
|
|||
#include "Luau/Ast.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
|
||||
static AstAttr* findAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
|
||||
{
|
||||
for (const auto attribute : attributes)
|
||||
{
|
||||
if (attribute->type == attributeType)
|
||||
return true;
|
||||
return attribute;
|
||||
}
|
||||
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool hasAttributeInArray(const AstArray<AstAttr*> attributes, AstAttr::Type attributeType)
|
||||
{
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
return findAttributeInArray(attributes, attributeType) != nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto attribute : attributes)
|
||||
{
|
||||
if (attribute->type == attributeType)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
||||
|
@ -26,9 +47,10 @@ static void visitTypeList(AstVisitor* visitor, const AstTypeList& list)
|
|||
list.tailType->visit(visitor);
|
||||
}
|
||||
|
||||
AstAttr::AstAttr(const Location& location, Type type)
|
||||
AstAttr::AstAttr(const Location& location, Type type, AstArray<AstExpr*> args)
|
||||
: AstNode(ClassIndex(), location)
|
||||
, type(type)
|
||||
, args(args)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -37,6 +59,29 @@ void AstAttr::visit(AstVisitor* visitor)
|
|||
visitor->visit(this);
|
||||
}
|
||||
|
||||
AstAttr::DeprecatedInfo AstAttr::deprecatedInfo() const
|
||||
{
|
||||
AstAttr::DeprecatedInfo info;
|
||||
info.deprecated = type == AstAttr::Type::Deprecated;
|
||||
|
||||
if (info.deprecated && args.size > 0)
|
||||
{
|
||||
AstExprTable* table = args.data[0]->as<AstExprTable>();
|
||||
if (auto useValue = table->getRecord("use"))
|
||||
{
|
||||
AstArray<char> use = (*useValue)->as<AstExprConstantString>()->value;
|
||||
info.use = {{use.data, use.size}};
|
||||
}
|
||||
if (auto reasonValue = table->getRecord("reason"))
|
||||
{
|
||||
AstArray<char> reason = (*reasonValue)->as<AstExprConstantString>()->value;
|
||||
info.reason = {{reason.data, reason.size}};
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
int gAstRttiIndex = 0;
|
||||
|
||||
AstGenericType::AstGenericType(const Location& location, AstName name, AstType* defaultValue)
|
||||
|
@ -293,6 +338,11 @@ bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const
|
|||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstAttr* AstExprFunction::getAttribute(const AstAttr::Type attributeType) const
|
||||
{
|
||||
return findAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstExprTable::AstExprTable(const Location& location, const AstArray<Item>& items)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, items(items)
|
||||
|
@ -313,6 +363,19 @@ void AstExprTable::visit(AstVisitor* visitor)
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<AstExpr*> AstExprTable::getRecord(const char* key) const
|
||||
{
|
||||
for (const AstExprTable::Item& item : items)
|
||||
{
|
||||
if (item.kind == AstExprTable::Item::Kind::Record)
|
||||
{
|
||||
if (strcmp(item.key->as<AstExprConstantString>()->value.data, key) == 0)
|
||||
return item.value;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
AstExprUnary::AstExprUnary(const Location& location, Op op, AstExpr* expr)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, op(op)
|
||||
|
@ -917,6 +980,11 @@ bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const
|
|||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstAttr* AstStatDeclareFunction::getAttribute(const AstAttr::Type attributeType) const
|
||||
{
|
||||
return findAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstStatDeclareExternType::AstStatDeclareExternType(
|
||||
const Location& location,
|
||||
const AstName& name,
|
||||
|
@ -1085,6 +1153,11 @@ bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const
|
|||
return hasAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstAttr* AstTypeFunction::getAttribute(AstAttr::Type attributeType) const
|
||||
{
|
||||
return findAttributeInArray(attributes, attributeType);
|
||||
}
|
||||
|
||||
AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr)
|
||||
: AstType(ClassIndex(), location)
|
||||
, expr(expr)
|
||||
|
@ -1234,6 +1307,34 @@ bool isLValue(const AstExpr* expr)
|
|||
return expr->is<AstExprLocal>() || expr->is<AstExprGlobal>() || expr->is<AstExprIndexName>() || expr->is<AstExprIndexExpr>();
|
||||
}
|
||||
|
||||
bool isConstantLiteral(const AstExpr* expr)
|
||||
{
|
||||
return expr->is<AstExprConstantNil>() || expr->is<AstExprConstantBool>() || expr->is<AstExprConstantNumber>() ||
|
||||
expr->is<AstExprConstantString>();
|
||||
}
|
||||
|
||||
bool isLiteralTable(const AstExpr* expr)
|
||||
{
|
||||
if (!expr->is<AstExprTable>())
|
||||
return false;
|
||||
|
||||
for (const AstExprTable::Item& item : expr->as<AstExprTable>()->items)
|
||||
{
|
||||
switch (item.kind)
|
||||
{
|
||||
case AstExprTable::Item::Kind::General:
|
||||
return false;
|
||||
break;
|
||||
case AstExprTable::Item::Kind::Record:
|
||||
case AstExprTable::Item::Kind::List:
|
||||
if (!isConstantLiteral(item.value) && !isLiteralTable(item.value))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AstName getIdentifier(AstExpr* node)
|
||||
{
|
||||
if (AstExprGlobal* expr = node->as<AstExprGlobal>())
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <limits.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -147,6 +149,9 @@ std::string Lexeme::toString() const
|
|||
case Attribute:
|
||||
return name ? format("'%s'", name) : "attribute";
|
||||
|
||||
case AttributeOpen:
|
||||
return "'@['";
|
||||
|
||||
case BrokenString:
|
||||
return "malformed string";
|
||||
|
||||
|
@ -981,8 +986,36 @@ Lexeme Lexer::readNext()
|
|||
}
|
||||
case '@':
|
||||
{
|
||||
std::pair<AstName, Lexeme::Type> attribute = readName();
|
||||
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
|
||||
if (FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
if (peekch(1) == '[')
|
||||
{
|
||||
consume();
|
||||
consume();
|
||||
|
||||
return Lexeme(Location(start, 2), Lexeme::AttributeOpen);
|
||||
}
|
||||
else
|
||||
{
|
||||
// consume @ first
|
||||
consume();
|
||||
|
||||
if (isAlpha(peekch()) || peekch() == '_')
|
||||
{
|
||||
std::pair<AstName, Lexeme::Type> attribute = readName();
|
||||
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Lexeme(Location(start, position()), Lexeme::Attribute, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::pair<AstName, Lexeme::Type> attribute = readName();
|
||||
return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value);
|
||||
}
|
||||
}
|
||||
default:
|
||||
if (isDigit(peekch()))
|
||||
|
|
|
@ -20,6 +20,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
|||
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax)
|
||||
|
||||
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
|
||||
bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false;
|
||||
|
@ -27,17 +28,65 @@ bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false;
|
|||
namespace Luau
|
||||
{
|
||||
|
||||
using AttributeArgumentsValidator = std::function<std::vector<std::pair<Location, std::string>>(Location, const AstArray<AstExpr*>&)>;
|
||||
|
||||
struct AttributeEntry
|
||||
{
|
||||
const char* name;
|
||||
AstAttr::Type type;
|
||||
std::optional<AttributeArgumentsValidator> argsValidator;
|
||||
};
|
||||
|
||||
std::vector<std::pair<Location, std::string>> deprecatedArgsValidator(Location attrLoc, const AstArray<AstExpr*>& args)
|
||||
{
|
||||
|
||||
if (args.size == 0)
|
||||
return {};
|
||||
if (args.size > 1)
|
||||
return {{attrLoc, "@deprecated can be parametrized only by 1 argument"}};
|
||||
|
||||
if (!args.data[0]->is<AstExprTable>())
|
||||
return {{args.data[0]->location, "Unknown argument type for @deprecated"}};
|
||||
|
||||
std::vector<std::pair<Location, std::string>> errors;
|
||||
for (const AstExprTable::Item& item : args.data[0]->as<AstExprTable>()->items)
|
||||
{
|
||||
if (item.kind == AstExprTable::Item::Kind::Record)
|
||||
{
|
||||
AstArray<char> keyString = item.key->as<AstExprConstantString>()->value;
|
||||
std::string key(keyString.data, keyString.size);
|
||||
if (key != "use" && key != "reason")
|
||||
{
|
||||
errors.emplace_back(
|
||||
item.key->location,
|
||||
format("Unknown argument '%s' for @deprecated. Only string constants for 'use' and 'reason' are allowed", key.c_str())
|
||||
);
|
||||
}
|
||||
else if (!item.value->is<AstExprConstantString>())
|
||||
{
|
||||
errors.emplace_back(item.value->location, format("Only constant string allowed as value for '%s'", key.c_str()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.emplace_back(item.value->location, "Only constants keys 'use' and 'reason' are allowed for @deprecated attribute");
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
AttributeEntry kAttributeEntries_DEPRECATED[] = {
|
||||
{"@checked", AstAttr::Type::Checked, {}},
|
||||
{"@native", AstAttr::Type::Native, {}},
|
||||
{"@deprecated", AstAttr::Type::Deprecated, {}},
|
||||
{nullptr, AstAttr::Type::Checked, {}}
|
||||
};
|
||||
|
||||
AttributeEntry kAttributeEntries[] = {
|
||||
{"@checked", AstAttr::Type::Checked},
|
||||
{"@native", AstAttr::Type::Native},
|
||||
{"@deprecated", AstAttr::Type::Deprecated},
|
||||
{nullptr, AstAttr::Type::Checked}
|
||||
{"checked", AstAttr::Type::Checked, {}},
|
||||
{"native", AstAttr::Type::Native, {}},
|
||||
{"deprecated", AstAttr::Type::Deprecated, deprecatedArgsValidator},
|
||||
{nullptr, AstAttr::Type::Checked, {}}
|
||||
};
|
||||
|
||||
ParseError::ParseError(const Location& location, std::string message)
|
||||
|
@ -359,6 +408,7 @@ AstStat* Parser::parseStat()
|
|||
case Lexeme::ReservedBreak:
|
||||
return parseBreak();
|
||||
case Lexeme::Attribute:
|
||||
case Lexeme::AttributeOpen:
|
||||
return parseAttributeStat();
|
||||
default:;
|
||||
}
|
||||
|
@ -768,16 +818,65 @@ AstStat* Parser::parseFunctionStat(const AstArray<AstAttr*>& attributes)
|
|||
return node;
|
||||
}
|
||||
|
||||
std::optional<AstAttr::Type> Parser::validateAttribute(const char* attributeName, const TempVector<AstAttr*>& attributes)
|
||||
std::optional<AstAttr::Type> Parser::validateAttribute(
|
||||
Location loc,
|
||||
const char* attributeName,
|
||||
const TempVector<AstAttr*>& attributes,
|
||||
const AstArray<AstExpr*>& args
|
||||
)
|
||||
{
|
||||
// check if the attribute name is valid
|
||||
std::optional<AstAttr::Type> type;
|
||||
std::optional<AttributeArgumentsValidator> argsValidator;
|
||||
|
||||
for (int i = 0; kAttributeEntries[i].name; ++i)
|
||||
{
|
||||
if (strcmp(attributeName, kAttributeEntries[i].name) == 0)
|
||||
{
|
||||
type = kAttributeEntries[i].type;
|
||||
argsValidator = kAttributeEntries[i].argsValidator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!type)
|
||||
{
|
||||
if (strlen(attributeName) == 0)
|
||||
report(loc, "Attribute name is missing");
|
||||
else
|
||||
report(loc, "Invalid attribute '@%s'", attributeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check that attribute is not duplicated
|
||||
for (const AstAttr* attr : attributes)
|
||||
{
|
||||
if (attr->type == *type)
|
||||
report(loc, "Cannot duplicate attribute '@%s'", attributeName);
|
||||
}
|
||||
if (argsValidator)
|
||||
{
|
||||
auto errorsToReport = (*argsValidator)(loc, args);
|
||||
for (const auto& [errorLoc, msg] : errorsToReport)
|
||||
{
|
||||
report(errorLoc, "%s", msg.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
std::optional<AstAttr::Type> Parser::validateAttribute_DEPRECATED(const char* attributeName, const TempVector<AstAttr*>& attributes)
|
||||
{
|
||||
// check if the attribute name is valid
|
||||
std::optional<AstAttr::Type> type;
|
||||
|
||||
for (int i = 0; kAttributeEntries_DEPRECATED[i].name; ++i)
|
||||
{
|
||||
if (strcmp(attributeName, kAttributeEntries_DEPRECATED[i].name) == 0)
|
||||
{
|
||||
type = kAttributeEntries_DEPRECATED[i].type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -805,17 +904,93 @@ std::optional<AstAttr::Type> Parser::validateAttribute(const char* attributeName
|
|||
// attribute ::= '@' NAME
|
||||
void Parser::parseAttribute(TempVector<AstAttr*>& attributes)
|
||||
{
|
||||
LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute);
|
||||
AstArray<AstExpr*> empty;
|
||||
if (!FFlag::LuauParametrizedAttributeSyntax)
|
||||
{
|
||||
LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute);
|
||||
|
||||
Location loc = lexer.current().location;
|
||||
Location loc = lexer.current().location;
|
||||
|
||||
const char* name = lexer.current().name;
|
||||
std::optional<AstAttr::Type> type = validateAttribute(name, attributes);
|
||||
const char* name = lexer.current().name;
|
||||
std::optional<AstAttr::Type> type = validateAttribute_DEPRECATED(name, attributes);
|
||||
|
||||
nextLexeme();
|
||||
nextLexeme();
|
||||
|
||||
if (type)
|
||||
attributes.push_back(allocator.alloc<AstAttr>(loc, *type));
|
||||
if (type)
|
||||
attributes.push_back(allocator.alloc<AstAttr>(loc, *type, empty));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute || lexer.current().type == Lexeme::Type::AttributeOpen);
|
||||
|
||||
if (lexer.current().type == Lexeme::Type::Attribute)
|
||||
{
|
||||
Location loc = lexer.current().location;
|
||||
|
||||
const char* name = lexer.current().name;
|
||||
std::optional<AstAttr::Type> type = validateAttribute(loc, name, attributes, empty);
|
||||
|
||||
nextLexeme();
|
||||
|
||||
if (type)
|
||||
attributes.push_back(allocator.alloc<AstAttr>(loc, *type, empty));
|
||||
}
|
||||
else
|
||||
{
|
||||
Lexeme open = lexer.current();
|
||||
nextLexeme();
|
||||
|
||||
if (lexer.current().type != ']')
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Name name = parseName("attribute name");
|
||||
|
||||
Location nameLoc = name.location;
|
||||
const char* attrName = name.name.value;
|
||||
|
||||
if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || lexer.current().type == '{' ||
|
||||
lexer.current().type == '(')
|
||||
{
|
||||
|
||||
auto [args, argsLocation, _exprLocation] = parseCallList(nullptr);
|
||||
|
||||
for (const AstExpr* arg : args)
|
||||
{
|
||||
if (!isConstantLiteral(arg) && !isLiteralTable(arg))
|
||||
report(argsLocation, "Only literals can be passed as arguments for attributes");
|
||||
}
|
||||
|
||||
std::optional<AstAttr::Type> type = validateAttribute(nameLoc, attrName, attributes, args);
|
||||
|
||||
if (type)
|
||||
attributes.push_back(allocator.alloc<AstAttr>(Location(nameLoc, argsLocation), *type, args));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::optional<AstAttr::Type> type = validateAttribute(nameLoc, attrName, attributes, empty);
|
||||
if (type)
|
||||
attributes.push_back(allocator.alloc<AstAttr>(nameLoc, *type, empty));
|
||||
}
|
||||
|
||||
if (lexer.current().type == ',')
|
||||
{
|
||||
nextLexeme();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
report(Location(open.location, lexer.current().location), "Attribute list cannot be empty");
|
||||
}
|
||||
|
||||
expectMatchAndConsume(']', open);
|
||||
}
|
||||
}
|
||||
|
||||
// attributes ::= {attribute}
|
||||
|
@ -823,11 +998,11 @@ AstArray<AstAttr*> Parser::parseAttributes()
|
|||
{
|
||||
Lexeme::Type type = lexer.current().type;
|
||||
|
||||
LUAU_ASSERT(type == Lexeme::Attribute);
|
||||
LUAU_ASSERT(type == Lexeme::Attribute || type == Lexeme::AttributeOpen);
|
||||
|
||||
TempVector<AstAttr*> attributes(scratchAttr);
|
||||
|
||||
while (lexer.current().type == Lexeme::Attribute)
|
||||
while (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen))
|
||||
parseAttribute(attributes);
|
||||
|
||||
return copy(attributes);
|
||||
|
@ -1235,7 +1410,8 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
|
|||
{
|
||||
AstArray<AstAttr*> attributes{nullptr, 0};
|
||||
|
||||
if (lexer.current().type == Lexeme::Attribute)
|
||||
if (lexer.current().type == Lexeme::Attribute ||
|
||||
(FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen))
|
||||
{
|
||||
attributes = Parser::parseAttributes();
|
||||
|
||||
|
@ -2350,7 +2526,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext)
|
|||
|
||||
AstArray<AstAttr*> attributes{nullptr, 0};
|
||||
|
||||
if (lexer.current().type == Lexeme::Attribute)
|
||||
if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen))
|
||||
{
|
||||
if (!inDeclarationContext)
|
||||
{
|
||||
|
@ -2997,7 +3173,7 @@ AstExpr* Parser::parseSimpleExpr()
|
|||
|
||||
AstArray<AstAttr*> attributes{nullptr, 0};
|
||||
|
||||
if (lexer.current().type == Lexeme::Attribute)
|
||||
if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen))
|
||||
{
|
||||
attributes = parseAttributes();
|
||||
|
||||
|
@ -3086,6 +3262,47 @@ AstExpr* Parser::parseSimpleExpr()
|
|||
}
|
||||
}
|
||||
|
||||
std::tuple<AstArray<AstExpr*>, Location, Location> Parser::parseCallList(TempVector<Position>* commaPositions)
|
||||
{
|
||||
LUAU_ASSERT(
|
||||
lexer.current().type == '(' || lexer.current().type == '{' || lexer.current().type == Lexeme::RawString ||
|
||||
lexer.current().type == Lexeme::QuotedString
|
||||
);
|
||||
if (lexer.current().type == '(')
|
||||
{
|
||||
Position argStart = lexer.current().location.end;
|
||||
|
||||
MatchLexeme matchParen = lexer.current();
|
||||
nextLexeme();
|
||||
|
||||
TempVector<AstExpr*> args(scratchExpr);
|
||||
|
||||
if (lexer.current().type != ')')
|
||||
parseExprList(args, commaPositions);
|
||||
|
||||
Location end = lexer.current().location;
|
||||
Position argEnd = end.end;
|
||||
|
||||
expectMatchAndConsume(')', matchParen);
|
||||
|
||||
return {copy(args), Location(argStart, argEnd), Location(matchParen.position, lexer.previousLocation().begin)};
|
||||
}
|
||||
else if (lexer.current().type == '{')
|
||||
{
|
||||
Position argStart = lexer.current().location.end;
|
||||
AstExpr* expr = parseTableConstructor();
|
||||
Position argEnd = lexer.previousLocation().end;
|
||||
|
||||
return {copy(&expr, 1), Location(argStart, argEnd), expr->location};
|
||||
}
|
||||
else
|
||||
{
|
||||
Location argLocation = lexer.current().location;
|
||||
AstExpr* expr = parseString();
|
||||
return {copy(&expr, 1), argLocation, expr->location};
|
||||
}
|
||||
}
|
||||
|
||||
// args ::= `(' [explist] `)' | tableconstructor | String
|
||||
AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self)
|
||||
{
|
||||
|
|
|
@ -54,6 +54,7 @@ struct Reducer
|
|||
ParseOptions parseOptions;
|
||||
|
||||
ParseResult parseResult;
|
||||
CstNodeMap cstNodeMap{nullptr};
|
||||
AstStatBlock* root;
|
||||
|
||||
std::string scriptName;
|
||||
|
@ -64,6 +65,7 @@ struct Reducer
|
|||
Reducer()
|
||||
{
|
||||
parseOptions.captureComments = true;
|
||||
parseOptions.storeCstData = true;
|
||||
}
|
||||
|
||||
std::string readLine(FILE* f)
|
||||
|
@ -83,7 +85,7 @@ struct Reducer
|
|||
|
||||
void writeTempScript(bool minify = false)
|
||||
{
|
||||
std::string source = transpileWithTypes(*root);
|
||||
std::string source = transpileWithTypes(*root, cstNodeMap);
|
||||
|
||||
if (minify)
|
||||
{
|
||||
|
@ -454,6 +456,7 @@ struct Reducer
|
|||
}
|
||||
|
||||
root = parseResult.root;
|
||||
cstNodeMap = std::move(parseResult.cstNodeMap);
|
||||
|
||||
const TestResult initialResult = run();
|
||||
if (initialResult == TestResult::NoBug)
|
||||
|
|
|
@ -168,8 +168,6 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname
|
|||
{
|
||||
if (lua_gettop(ML) == 0)
|
||||
lua_pushstring(ML, "module must return a value");
|
||||
else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
|
||||
lua_pushstring(ML, "module must return a table or function");
|
||||
}
|
||||
else if (status == LUA_YIELD)
|
||||
{
|
||||
|
|
|
@ -10,8 +10,8 @@ namespace CodeGen
|
|||
|
||||
struct IrBuilder;
|
||||
|
||||
void constPropInBlockChains(IrBuilder& build, bool useValueNumbering);
|
||||
void createLinearBlocks(IrBuilder& build, bool useValueNumbering);
|
||||
void constPropInBlockChains(IrBuilder& build);
|
||||
void createLinearBlocks(IrBuilder& build);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodeGenAllocationCheck)
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
|
@ -54,16 +52,6 @@ static void freePagesImpl(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"failed to deallocate block memory");
|
||||
}
|
||||
|
||||
static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
DWORD oldProtect;
|
||||
if (VirtualProtect(mem, size, PAGE_EXECUTE_READ, &oldProtect) == 0)
|
||||
CODEGEN_ASSERT(!"Failed to change page protection");
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
|
@ -102,15 +90,6 @@ static void freePagesImpl(uint8_t* mem, size_t size)
|
|||
CODEGEN_ASSERT(!"Failed to deallocate block memory");
|
||||
}
|
||||
|
||||
static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
CODEGEN_ASSERT(size == alignToPageSize(size));
|
||||
|
||||
if (mprotect(mem, size, PROT_READ | PROT_EXEC) != 0)
|
||||
CODEGEN_ASSERT(!"Failed to change page protection");
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size)
|
||||
{
|
||||
CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0);
|
||||
|
@ -203,15 +182,8 @@ bool CodeAllocator::allocate(
|
|||
|
||||
size_t pageAlignedSize = alignToPageSize(startOffset + totalSize);
|
||||
|
||||
if (FFlag::LuauCodeGenAllocationCheck)
|
||||
{
|
||||
if (!makePagesExecutable(blockPos, pageAlignedSize))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
makePagesExecutable_DEPRECATED(blockPos, pageAlignedSize);
|
||||
}
|
||||
if (!makePagesExecutable(blockPos, pageAlignedSize))
|
||||
return false;
|
||||
|
||||
flushInstructionCache(blockPos + codeOffset, codeSize);
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt)
|
||||
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize)
|
||||
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering)
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@
|
|||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
LUAU_FASTFLAG(DebugCodegenNoOpt)
|
||||
LUAU_FASTFLAG(DebugCodegenOptSize)
|
||||
LUAU_FASTFLAG(DebugCodegenSkipNumbering)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
|
||||
|
@ -335,36 +333,31 @@ inline bool lowerFunction(
|
|||
|
||||
computeCfgInfo(ir.function);
|
||||
|
||||
if (!FFlag::DebugCodegenNoOpt)
|
||||
constPropInBlockChains(ir);
|
||||
|
||||
if (!FFlag::DebugCodegenOptSize)
|
||||
{
|
||||
bool useValueNumbering = !FFlag::DebugCodegenSkipNumbering;
|
||||
double startTime = 0.0;
|
||||
unsigned constPropInstructionCount = 0;
|
||||
|
||||
constPropInBlockChains(ir, useValueNumbering);
|
||||
|
||||
if (!FFlag::DebugCodegenOptSize)
|
||||
if (stats)
|
||||
{
|
||||
double startTime = 0.0;
|
||||
unsigned constPropInstructionCount = 0;
|
||||
|
||||
if (stats)
|
||||
{
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE);
|
||||
startTime = lua_clock();
|
||||
}
|
||||
|
||||
createLinearBlocks(ir, useValueNumbering);
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime;
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount;
|
||||
stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount;
|
||||
}
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE);
|
||||
startTime = lua_clock();
|
||||
}
|
||||
|
||||
markDeadStoresInBlockChains(ir);
|
||||
createLinearBlocks(ir);
|
||||
|
||||
if (stats)
|
||||
{
|
||||
stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime;
|
||||
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount;
|
||||
stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount;
|
||||
}
|
||||
}
|
||||
|
||||
markDeadStoresInBlockChains(ir);
|
||||
|
||||
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(ir.function);
|
||||
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
|
|
|
@ -305,8 +305,6 @@ struct ConstPropState
|
|||
|
||||
uint32_t* getPreviousInstIndex(const IrInst& inst)
|
||||
{
|
||||
CODEGEN_ASSERT(useValueNumbering);
|
||||
|
||||
if (uint32_t* prevIdx = valueMap.find(inst))
|
||||
{
|
||||
// Previous load might have been removed as unused
|
||||
|
@ -325,7 +323,7 @@ struct ConstPropState
|
|||
|
||||
std::pair<IrCmd, uint32_t> getPreviousVersionedLoadForTag(uint8_t tag, IrOp vmReg)
|
||||
{
|
||||
if (useValueNumbering && !function.cfg.captured.regs.test(vmRegOp(vmReg)))
|
||||
if (!function.cfg.captured.regs.test(vmRegOp(vmReg)))
|
||||
{
|
||||
if (tag == LUA_TBOOLEAN)
|
||||
{
|
||||
|
@ -350,9 +348,6 @@ struct ConstPropState
|
|||
// Find existing value of the instruction that is exactly the same, or record current on for future lookups
|
||||
void substituteOrRecord(IrInst& inst, uint32_t instIdx)
|
||||
{
|
||||
if (!useValueNumbering)
|
||||
return;
|
||||
|
||||
if (uint32_t* prevIdx = getPreviousInstIndex(inst))
|
||||
{
|
||||
substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx});
|
||||
|
@ -368,9 +363,6 @@ struct ConstPropState
|
|||
{
|
||||
CODEGEN_ASSERT(loadInst.a.kind == IrOpKind::VmReg);
|
||||
|
||||
if (!useValueNumbering)
|
||||
return;
|
||||
|
||||
// To avoid captured register invalidation tracking in lowering later, values from loads from captured registers are not propagated
|
||||
// This prevents the case where load value location is linked to memory in case of a spill and is then clobbered in a user call
|
||||
if (function.cfg.captured.regs.test(vmRegOp(loadInst.a)))
|
||||
|
@ -405,9 +397,6 @@ struct ConstPropState
|
|||
CODEGEN_ASSERT(storeInst.a.kind == IrOpKind::VmReg);
|
||||
CODEGEN_ASSERT(storeInst.b.kind == IrOpKind::Inst);
|
||||
|
||||
if (!useValueNumbering)
|
||||
return;
|
||||
|
||||
// To avoid captured register invalidation tracking in lowering later, values from stores into captured registers are not propagated
|
||||
// This prevents the case where store creates an alternative value location in case of a spill and is then clobbered in a user call
|
||||
if (function.cfg.captured.regs.test(vmRegOp(storeInst.a)))
|
||||
|
@ -494,8 +483,6 @@ struct ConstPropState
|
|||
|
||||
IrFunction& function;
|
||||
|
||||
bool useValueNumbering = false;
|
||||
|
||||
std::array<RegisterInfo, 256> regs;
|
||||
|
||||
// For range/full invalidations, we only want to visit a limited number of data that we have recorded
|
||||
|
@ -1858,12 +1845,11 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector<uint8_t>& visited
|
|||
constPropInBlock(build, linearBlock, state);
|
||||
}
|
||||
|
||||
void constPropInBlockChains(IrBuilder& build, bool useValueNumbering)
|
||||
void constPropInBlockChains(IrBuilder& build)
|
||||
{
|
||||
IrFunction& function = build.function;
|
||||
|
||||
ConstPropState state{function};
|
||||
state.useValueNumbering = useValueNumbering;
|
||||
|
||||
std::vector<uint8_t> visited(function.blocks.size(), false);
|
||||
|
||||
|
@ -1879,7 +1865,7 @@ void constPropInBlockChains(IrBuilder& build, bool useValueNumbering)
|
|||
}
|
||||
}
|
||||
|
||||
void createLinearBlocks(IrBuilder& build, bool useValueNumbering)
|
||||
void createLinearBlocks(IrBuilder& build)
|
||||
{
|
||||
// Go through internal block chains and outline them into a single new block.
|
||||
// Outlining will be able to linearize the execution, even if there was a jump to a block with multiple users,
|
||||
|
@ -1887,7 +1873,6 @@ void createLinearBlocks(IrBuilder& build, bool useValueNumbering)
|
|||
IrFunction& function = build.function;
|
||||
|
||||
ConstPropState state{function};
|
||||
state.useValueNumbering = useValueNumbering;
|
||||
|
||||
std::vector<uint8_t> visited(function.blocks.size(), false);
|
||||
|
||||
|
|
|
@ -28,8 +28,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
|||
|
||||
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileCli162537)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -1930,10 +1928,7 @@ struct Compiler
|
|||
|
||||
LUAU_ASSERT(shape.length < BytecodeBuilder::TableShape::kMaxLength);
|
||||
|
||||
if (FFlag::LuauCompileCli162537)
|
||||
shape.keys[shape.length++] = cid;
|
||||
else
|
||||
shape.keys[shape.length++] = int16_t(cid);
|
||||
shape.keys[shape.length++] = cid;
|
||||
}
|
||||
|
||||
int32_t tid = bytecode.addConstantTable(shape);
|
||||
|
|
|
@ -227,6 +227,7 @@ LUA_API int lua_setfenv(lua_State* L, int idx);
|
|||
LUA_API int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env);
|
||||
LUA_API void lua_call(lua_State* L, int nargs, int nresults);
|
||||
LUA_API int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
|
||||
LUA_API int lua_cpcall(lua_State* L, lua_CFunction func, void* ud);
|
||||
|
||||
/*
|
||||
** coroutine functions
|
||||
|
|
|
@ -1037,8 +1037,9 @@ void lua_call(lua_State* L, int nargs, int nresults)
|
|||
/*
|
||||
** Execute a protected call.
|
||||
*/
|
||||
// data to `f_call'
|
||||
struct CallS
|
||||
{ // data to `f_call'
|
||||
{
|
||||
StkId func;
|
||||
int nresults;
|
||||
};
|
||||
|
@ -1071,6 +1072,42 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc)
|
|||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
** Execute a protected C call.
|
||||
*/
|
||||
// data to `f_Ccall'
|
||||
struct CCallS
|
||||
{
|
||||
lua_CFunction func;
|
||||
void* ud;
|
||||
};
|
||||
|
||||
static void f_Ccall(lua_State* L, void* ud)
|
||||
{
|
||||
struct CCallS* c = cast_to(struct CCallS*, ud);
|
||||
|
||||
if (!lua_checkstack(L, 2))
|
||||
luaG_runerror(L, "stack limit");
|
||||
|
||||
lua_pushcclosurek(L, c->func, nullptr, 0, nullptr);
|
||||
lua_pushlightuserdata(L, c->ud);
|
||||
luaD_call(L, L->top - 2, 0);
|
||||
}
|
||||
|
||||
int lua_cpcall(lua_State* L, lua_CFunction func, void* ud)
|
||||
{
|
||||
api_check(L, L->status == 0);
|
||||
|
||||
struct CCallS c;
|
||||
c.func = func;
|
||||
c.ud = ud;
|
||||
|
||||
int status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0);
|
||||
|
||||
adjustresults(L, 0);
|
||||
return status;
|
||||
}
|
||||
|
||||
int lua_status(lua_State* L)
|
||||
{
|
||||
return L->status;
|
||||
|
|
|
@ -39,7 +39,6 @@ LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
|||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauErrorYield)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck)
|
||||
LUAU_FASTFLAG(LuauCompileCli162537)
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
|
@ -48,9 +47,6 @@ static lua_CompileOptions defaultOptions()
|
|||
copts.debugLevel = 1;
|
||||
copts.typeInfoLevel = 1;
|
||||
|
||||
copts.vectorCtor = "vector";
|
||||
copts.vectorType = "vector";
|
||||
|
||||
return copts;
|
||||
}
|
||||
|
||||
|
@ -108,21 +104,6 @@ static int lua_loadstring(lua_State* L)
|
|||
return 2; // return nil plus error message
|
||||
}
|
||||
|
||||
static int lua_vector(lua_State* L)
|
||||
{
|
||||
double x = luaL_checknumber(L, 1);
|
||||
double y = luaL_checknumber(L, 2);
|
||||
double z = luaL_checknumber(L, 3);
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
double w = luaL_optnumber(L, 4, 0.0);
|
||||
lua_pushvector(L, float(x), float(y), float(z), float(w));
|
||||
#else
|
||||
lua_pushvector(L, float(x), float(y), float(z));
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int lua_vector_dot(lua_State* L)
|
||||
{
|
||||
const float* a = luaL_checkvector(L, 1);
|
||||
|
@ -280,7 +261,6 @@ static StateRef runConformance(
|
|||
|
||||
// Lua conformance tests treat _G synonymously with getfenv(); for now cater to them
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_setfield(L, -1, "_G");
|
||||
|
||||
std::string chunkname = "=" + std::string(name);
|
||||
|
@ -314,6 +294,7 @@ static StateRef runConformance(
|
|||
{
|
||||
REQUIRE(lua_isstring(L, -1));
|
||||
CHECK(std::string(lua_tostring(L, -1)) == "OK");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -347,9 +328,6 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize)
|
|||
|
||||
void setupVectorHelpers(lua_State* L)
|
||||
{
|
||||
lua_pushcfunction(L, lua_vector, "vector");
|
||||
lua_setglobal(L, "vector");
|
||||
|
||||
#if LUA_VECTOR_SIZE == 4
|
||||
lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
#else
|
||||
|
@ -1816,6 +1794,23 @@ TEST_CASE("ApiIter")
|
|||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
static int cpcallTest(lua_State* L)
|
||||
{
|
||||
bool shouldFail = *(bool*)(lua_tolightuserdata(L, 1));
|
||||
|
||||
if (shouldFail)
|
||||
{
|
||||
luaL_error(L, "Failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pushinteger(L, 123);
|
||||
lua_setglobal(L, "cpcallvalue");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST_CASE("ApiCalls")
|
||||
{
|
||||
StateRef globalState = runConformance("apicalls.luau", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr));
|
||||
|
@ -1843,6 +1838,49 @@ TEST_CASE("ApiCalls")
|
|||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// lua_cpcall success
|
||||
{
|
||||
bool shouldFail = false;
|
||||
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_OK);
|
||||
CHECK(lua_status(L) == LUA_OK);
|
||||
|
||||
lua_getglobal(L, "cpcallvalue");
|
||||
CHECK(luaL_checkinteger(L, -1) == 123);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// lua_cpcall failure
|
||||
{
|
||||
bool shouldFail = true;
|
||||
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN);
|
||||
REQUIRE(lua_isstring(L, -1));
|
||||
CHECK(std::string(lua_tostring(L, -1)) == "Failed");
|
||||
lua_pop(L, 1);
|
||||
|
||||
CHECK(lua_status(L) == LUA_OK);
|
||||
}
|
||||
|
||||
// lua_cpcall early failure
|
||||
{
|
||||
bool shouldFail = false;
|
||||
|
||||
CHECK(lua_gettop(L) == 0);
|
||||
|
||||
luaL_checkstack(L, LUAI_MAXCSTACK - 1, "must succeed");
|
||||
|
||||
for (int i = 0; i < LUAI_MAXCSTACK - 1; i++)
|
||||
lua_pushnumber(L, 1.0);
|
||||
|
||||
CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN);
|
||||
REQUIRE(lua_isstring(L, -1));
|
||||
CHECK(std::string(lua_tostring(L, -1)) == "stack limit");
|
||||
lua_pop(L, 1);
|
||||
|
||||
CHECK(lua_status(L) == LUA_OK);
|
||||
|
||||
lua_pop(L, LUAI_MAXCSTACK - 1);
|
||||
}
|
||||
|
||||
// lua_equal with a sleeping thread wake up
|
||||
{
|
||||
lua_State* L2 = lua_newthread(L);
|
||||
|
@ -3078,14 +3116,42 @@ TEST_CASE("Native")
|
|||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
lua_CompileOptions copts = defaultOptions();
|
||||
|
||||
SUBCASE("Checked")
|
||||
{
|
||||
FFlag::DebugLuauAbortingChecks.value = true;
|
||||
|
||||
SUBCASE("O0")
|
||||
{
|
||||
copts.optimizationLevel = 0;
|
||||
}
|
||||
SUBCASE("O1")
|
||||
{
|
||||
copts.optimizationLevel = 1;
|
||||
}
|
||||
SUBCASE("O2")
|
||||
{
|
||||
copts.optimizationLevel = 2;
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Regular")
|
||||
{
|
||||
FFlag::DebugLuauAbortingChecks.value = false;
|
||||
|
||||
SUBCASE("O0")
|
||||
{
|
||||
copts.optimizationLevel = 0;
|
||||
}
|
||||
SUBCASE("O1")
|
||||
{
|
||||
copts.optimizationLevel = 1;
|
||||
}
|
||||
SUBCASE("O2")
|
||||
{
|
||||
copts.optimizationLevel = 2;
|
||||
}
|
||||
}
|
||||
|
||||
runConformance(
|
||||
|
@ -3093,7 +3159,10 @@ TEST_CASE("Native")
|
|||
[](lua_State* L)
|
||||
{
|
||||
setupNativeHelpers(L);
|
||||
}
|
||||
},
|
||||
nullptr,
|
||||
nullptr,
|
||||
&copts
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3329,8 +3398,6 @@ TEST_CASE("HugeFunctionLoadFailure")
|
|||
|
||||
TEST_CASE("HugeConstantTable")
|
||||
{
|
||||
ScopedFastFlag luauCompileCli162537{FFlag::LuauCompileCli162537, true};
|
||||
|
||||
std::string source = "function foo(...)\n";
|
||||
|
||||
source += " local args = ...\n";
|
||||
|
|
|
@ -30,7 +30,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
|
|||
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
|
||||
|
||||
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauTidyTypeUtils)
|
||||
LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete);
|
||||
|
||||
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
|
||||
|
||||
|
@ -153,12 +154,16 @@ struct Fixture
|
|||
// In that case, flag can be forced to 'true' using the example below:
|
||||
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
|
||||
ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true};
|
||||
ScopedFastFlag sff_LuauUpdateGetMetatableTypeSignature{FFlag::LuauUpdateGetMetatableTypeSignature, true};
|
||||
|
||||
ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true};
|
||||
|
||||
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
|
||||
// This is useful for tracking down violations of Luau's memory model.
|
||||
ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true};
|
||||
|
||||
// This makes sure that errant cases of constraint solving failing to complete still pop up in tests.
|
||||
ScopedFastFlag sff_DebugLuauAlwaysShowConstraintSolvingIncomplete{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, true};
|
||||
|
||||
TestFileResolver fileResolver;
|
||||
TestConfigResolver configResolver;
|
||||
NullModuleResolver moduleResolver;
|
||||
|
|
|
@ -156,7 +156,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
this->getFrontend().setLuauSolverMode(SolverMode::New);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -173,7 +173,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
this->getFrontend().setLuauSolverMode(SolverMode::Old);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -190,7 +190,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
)
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
this->getFrontend().setLuauSolverMode(SolverMode::New);
|
||||
this->check(document, getOptions());
|
||||
|
||||
FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -198,7 +198,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
|
|||
assertions(result);
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, false};
|
||||
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
this->getFrontend().setLuauSolverMode(SolverMode::Old);
|
||||
this->check(document, getOptions());
|
||||
|
||||
result = autocompleteFragment(updated, cursorPos, fragmentEndPosition);
|
||||
|
@ -1346,7 +1346,7 @@ t
|
|||
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().check("game/A", opts);
|
||||
CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr);
|
||||
CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr);
|
||||
|
@ -1428,7 +1428,7 @@ TEST_SUITE_BEGIN("MixedModeTests");
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local x = 4
|
||||
|
@ -1455,7 +1455,7 @@ local z = x + y
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local x = 4
|
||||
|
@ -1480,7 +1480,7 @@ local y = 5
|
|||
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
auto res = checkOldSolver(
|
||||
R"(
|
||||
local tbl = { abc = 1234}
|
||||
|
@ -1595,14 +1595,14 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(SolverMode::Old);
|
||||
checkAndExamine(source, "module", "{| |}");
|
||||
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{| |}", "{| a: (%error-id%: unknown) -> () |}");
|
||||
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{| |}", "{| ab: (%error-id%: unknown) -> () |}");
|
||||
}
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
getFrontend().setLuauSolverMode(SolverMode::New);
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
// [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on
|
||||
return;
|
||||
|
@ -2969,7 +2969,7 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(SolverMode::Old);
|
||||
checkAndExamine(source, "module", "{| |}");
|
||||
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||
// early return since the following checking will fail, which it shouldn't!
|
||||
|
@ -2979,7 +2979,7 @@ return module)";
|
|||
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
getFrontend().setLuauSolverMode(SolverMode::New);
|
||||
checkAndExamine(source, "module", "{ }");
|
||||
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
|
||||
// early return since the following checking will fail, which it shouldn't!
|
||||
|
|
|
@ -1353,7 +1353,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "separate_caches_for_autocomplete")
|
|||
|
||||
FrontendOptions opts;
|
||||
opts.forAutocomplete = true;
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().check("game/A", opts);
|
||||
|
||||
CHECK(nullptr == getFrontend().moduleResolver.getModule("game/A"));
|
||||
|
@ -1725,7 +1725,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_upda
|
|||
TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver")
|
||||
{
|
||||
ScopedFastFlag newSolver{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
|
||||
fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}";
|
||||
fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)";
|
||||
|
|
|
@ -17,6 +17,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
|
||||
|
||||
TEST_SUITE_BEGIN("Generalization");
|
||||
|
@ -229,7 +230,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
};
|
||||
|
||||
TableType tt;
|
||||
|
@ -266,7 +268,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
auto [aTy, aFree] = freshType();
|
||||
|
@ -429,7 +432,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "avoid_cross_module_mutation_in_bidirectional
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
fileResolver.source["Module/ListFns"] = R"(
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4);
|
||||
LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
TEST_SUITE_BEGIN("InferPolarity");
|
||||
|
||||
|
@ -18,7 +18,8 @@ TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
TypeArena arena;
|
||||
|
@ -58,7 +59,7 @@ TEST_CASE_FIXTURE(Fixture, "<a, b>({ read x: a, write x: b }) -> ()")
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauInferPolarityOfReadWriteProperties, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
};
|
||||
|
||||
TypeArena arena;
|
||||
|
|
|
@ -881,7 +881,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -925,7 +925,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -954,7 +954,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -981,7 +981,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1020,7 +1020,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1066,7 +1066,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1098,7 +1098,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1129,7 +1129,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1186,7 +1186,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1219,7 +1219,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1249,7 +1249,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1281,7 +1281,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1317,7 +1317,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(3));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1356,7 +1356,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(3));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1391,7 +1391,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1423,7 +1423,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1454,7 +1454,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1482,7 +1482,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1515,7 +1515,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUnique
|
|||
build.inst(IrCmd::JUMP, block2);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1551,7 +1551,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval")
|
|||
build.inst(IrCmd::JUMP, entry);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1586,7 +1586,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1")
|
|||
build.inst(IrCmd::JUMP, block);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1628,7 +1628,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2")
|
|||
build.inst(IrCmd::JUMP, block);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1664,7 +1664,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes")
|
|||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1700,7 +1700,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1744,7 +1744,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(9));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1809,8 +1809,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
createLinearBlocks(build, true);
|
||||
constPropInBlockChains(build);
|
||||
createLinearBlocks(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1885,8 +1885,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues"
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
createLinearBlocks(build, true);
|
||||
constPropInBlockChains(build);
|
||||
createLinearBlocks(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1937,8 +1937,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis")
|
|||
build.inst(IrCmd::JUMP, block2);
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
createLinearBlocks(build, true);
|
||||
constPropInBlockChains(build);
|
||||
createLinearBlocks(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1967,7 +1967,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -1995,7 +1995,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VaridicRegisterRangeInvalidation")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2021,7 +2021,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType")
|
|||
build.inst(IrCmd::RETURN, build.constUint(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2065,7 +2065,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
|
@ -2131,7 +2131,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2192,7 +2192,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksInvalidation")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
|
@ -2250,7 +2250,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
|
@ -2310,7 +2310,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
// In the future, we might even see duplicate identical TValue loads go away
|
||||
// In the future, we might even see loads of different VM regs with the same value go away
|
||||
|
@ -2368,7 +2368,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2424,7 +2424,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2480,7 +2480,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2540,7 +2540,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2584,7 +2584,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLenghtChecksNegativeIndex")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2618,7 +2618,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"(
|
||||
bb_0: ; useCount: 0
|
||||
|
@ -2655,7 +2655,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepInvalidation")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2687,7 +2687,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2710,7 +2710,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2735,7 +2735,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2757,7 +2757,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -2783,7 +2783,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3259,7 +3259,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RemoveDuplicateCalculation")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3295,7 +3295,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LateTableStateLink")
|
|||
build.inst(IrCmd::RETURN, build.constUint(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3326,7 +3326,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RegisterVersioning")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3355,7 +3355,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetListIsABlocker")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3384,7 +3384,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "CallIsABlocker")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3413,7 +3413,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPropagationOfCapturedRegs")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
; captured regs: R0
|
||||
|
@ -3445,7 +3445,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadLoadReuse")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3472,7 +3472,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadValueReuse")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3514,7 +3514,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3547,7 +3547,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3575,7 +3575,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval")
|
|||
build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3606,7 +3606,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval")
|
|||
build.inst(IrCmd::RETURN, build.constUint(2));
|
||||
|
||||
updateUseCounts(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3647,7 +3647,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TaggedValuePropagationIntoTvalueChecksRegis
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
bb_0:
|
||||
|
@ -3705,7 +3705,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDoubleStore")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -3743,7 +3743,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturn")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -3766,7 +3766,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturnPartial")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Partial stores cannot be removed, even if unused
|
||||
|
@ -3795,7 +3795,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse1")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -3826,7 +3826,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse2")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Stores to pointers can be safely removed at 'return' point, but have to preserved for any GC assist trigger (such as a call)
|
||||
|
@ -3856,7 +3856,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse3")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Stores to pointers can be safely removed if there are no potential implicit uses by any GC assists
|
||||
|
@ -3883,7 +3883,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse4")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// It is important for tag overwrite to TNIL to kill not only the previous tag store, but the value as well
|
||||
|
@ -3914,7 +3914,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresWithRecombination")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -3939,7 +3939,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IgnoreFastcallAdjustment")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -3968,7 +3968,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "JumpImplicitLiveOut")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Even though bb_0 doesn't have R1 as a live out, chain optimization used the knowledge of those writes happening to optimize duplicate stores
|
||||
|
@ -4002,7 +4002,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "KeepCapturedRegisterStores")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Captured registers may be modified from called user functions (plain or hidden in metamethods)
|
||||
|
@ -4059,7 +4059,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "StoreCannotBeReplacedWithCheck")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
|
||||
|
@ -4115,7 +4115,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Even though R1 is not live in of the fallback, stack state cannot be left in a partial store state
|
||||
|
@ -4169,7 +4169,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks2")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
|
||||
|
@ -4281,7 +4281,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
|
||||
|
@ -4333,7 +4333,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag2")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag
|
||||
|
@ -4391,7 +4391,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotReturnWithPartialStores")
|
|||
|
||||
updateUseCounts(build.function);
|
||||
computeCfgInfo(build.function);
|
||||
constPropInBlockChains(build, true);
|
||||
constPropInBlockChains(build);
|
||||
markDeadStoresInBlockChains(build);
|
||||
|
||||
// Even though R1 is not live out at return, we stored table tag followed by an integer value
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2);
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4);
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -1873,6 +1874,191 @@ end
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeWithParams")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true};
|
||||
|
||||
// @deprecated works on local functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ use = "prodfun", reason = "Too old." }]
|
||||
local function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(
|
||||
result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead. Too old."
|
||||
);
|
||||
}
|
||||
|
||||
// @deprecated works on globals functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ use = "prodfun", reason = "Too old." }]
|
||||
function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(
|
||||
result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead. Too old."
|
||||
);
|
||||
}
|
||||
|
||||
// @deprecated with only 'use' works on local functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ use = "prodfun" }]
|
||||
local function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead");
|
||||
}
|
||||
|
||||
// @deprecated with only 'use' works on globals functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ use = "prodfun" }]
|
||||
function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead");
|
||||
}
|
||||
|
||||
|
||||
// @deprecated with only 'reason' works on local functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ reason = "Too old." }]
|
||||
local function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated. Too old.");
|
||||
}
|
||||
|
||||
// @deprecated with only 'reason' works on globals functions
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@[deprecated{ reason = "Too old." }]
|
||||
function testfun(x)
|
||||
return x + 1
|
||||
end
|
||||
|
||||
testfun(1)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated. Too old.");
|
||||
}
|
||||
|
||||
// @deprecated works for methods with a literal class name
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
Account = { balance=0 }
|
||||
|
||||
@[deprecated{use = 'credit', reason = 'It sounds cool'}]
|
||||
function Account:deposit(v)
|
||||
self.balance = self.balance + v
|
||||
end
|
||||
|
||||
Account:deposit(200.00)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated, use 'credit' instead. It sounds cool");
|
||||
}
|
||||
|
||||
// @deprecated works for methods with a compound expression class name
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
Account = { balance=0 }
|
||||
|
||||
function getAccount()
|
||||
return Account
|
||||
end
|
||||
|
||||
@[deprecated{use = 'credit', reason = 'It sounds cool'}]
|
||||
function Account:deposit (v)
|
||||
self.balance = self.balance + v
|
||||
end
|
||||
|
||||
(getAccount()):deposit(200.00)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated, use 'credit' instead. It sounds cool");
|
||||
}
|
||||
|
||||
{
|
||||
loadDefinition(R"(
|
||||
@[deprecated{use = 'foo', reason = 'Do better.'}] declare function bar(x: number): string
|
||||
)");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
bar(2)
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(1, 0), Position(1, 3), "Function 'bar' is deprecated, use 'foo' instead. Do better.");
|
||||
}
|
||||
|
||||
{
|
||||
loadDefinition(R"(
|
||||
declare Hooty : {
|
||||
tooty : @[deprecated{use = 'foo', reason = 'bar'}] @checked (number) -> number
|
||||
}
|
||||
)");
|
||||
LintResult result = lint(R"(
|
||||
print(Hooty:tooty(2.0))
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(1, 6), Position(1, 17), "Member 'Hooty.tooty' is deprecated, use 'foo' instead. bar");
|
||||
}
|
||||
|
||||
{
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
@[deprecated{use = 'foo', reason = 'baz'}]
|
||||
function bar(self, value: number) : number
|
||||
end
|
||||
|
||||
declare Foo: {
|
||||
new: () -> Foo
|
||||
}
|
||||
)");
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local foo = Foo.new()
|
||||
print(foo:bar(2.0))
|
||||
)");
|
||||
|
||||
REQUIRE(1 == result.warnings.size());
|
||||
checkDeprecatedWarning(result.warnings[0], Position(2, 6), Position(2, 13), "Member 'bar' is deprecated, use 'foo' instead. baz");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSolverV2, true};
|
||||
|
|
|
@ -327,6 +327,9 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden_n
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::DebugLuauMagicTypes, true},
|
||||
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
|
||||
// This debug flag is normally on, but we turn it off as we're testing
|
||||
// the exact behavior it enables.
|
||||
{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false},
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
|
|
|
@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
|
||||
LUAU_FASTINT(LuauNormalizeUnionLimit)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
using namespace Luau;
|
||||
|
@ -1222,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true},
|
||||
};
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
|
||||
|
|
|
@ -19,6 +19,7 @@ LUAU_FASTINT(LuauParseErrorLimit)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
|
||||
LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation)
|
||||
LUAU_FASTFLAG(LuauParametrizedAttributeSyntax)
|
||||
|
||||
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
|
||||
extern bool luau_telemetry_parsed_return_type_variadic_with_type_suffix;
|
||||
|
@ -3728,6 +3729,118 @@ end)");
|
|||
checkAttribute(attributes.data[0], AstAttr::Type::Checked, Location(Position(1, 0), Position(1, 8)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_parametrized_attribute_on_function_stat")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true};
|
||||
|
||||
AstStatBlock* stat = parse(R"(
|
||||
@[deprecated{ use = "greetng", reason = "Using <hello> is too causal"}]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
LUAU_ASSERT(stat != nullptr);
|
||||
|
||||
AstStatFunction* statFun = stat->body.data[0]->as<AstStatFunction>();
|
||||
LUAU_ASSERT(statFun != nullptr);
|
||||
|
||||
AstArray<AstAttr*> attributes = statFun->func->attributes;
|
||||
|
||||
CHECK_EQ(attributes.size, 1);
|
||||
|
||||
checkAttribute(attributes.data[0], AstAttr::Type::Deprecated, Location(Position(1, 2), Position(1, 70)));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "non_literal_attribute_arguments_is_not_allowed")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true};
|
||||
ParseResult result = tryParse(R"(
|
||||
@[deprecated{ reason = reasonString }]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors, 1, Location(Position(1, 13), Position(1, 37)), "Only literals can be passed as arguments for attributes"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "unknown_arguments_for_depricated_is_not_allowed")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true};
|
||||
ParseResult result = tryParse(R"(
|
||||
@[deprecated({}, "Very deprecated")]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 2), Position(1, 12)), "@deprecated can be parametrized only by 1 argument");
|
||||
|
||||
result = tryParse(R"(
|
||||
@[deprecated "Very deprecated"]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 13), Position(1, 30)), "Unknown argument type for @deprecated");
|
||||
|
||||
result = tryParse(R"(
|
||||
@[deprecated{ foo = "bar" }]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors,
|
||||
1,
|
||||
Location(Position(1, 14), Position(1, 17)),
|
||||
"Unknown argument 'foo' for @deprecated. Only string constants for 'use' and 'reason' are allowed"
|
||||
);
|
||||
|
||||
result = tryParse(R"(
|
||||
@[deprecated{ use = 5 }]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
|
||||
checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 20), Position(1, 21)), "Only constant string allowed as value for 'use'");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "do_not_hang_on_incomplete_attribute_list")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true};
|
||||
ParseResult result = tryParse(R"(
|
||||
@[]
|
||||
function hello(x, y)
|
||||
return x + y
|
||||
end)");
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors, 1, Location(Position(1, 0), Position(1, 3)), "Attribute list cannot be empty"
|
||||
);
|
||||
|
||||
result = tryParse(R"(@[)");
|
||||
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors, 1, Location(Position(0, 2), Position(0, 2)), "Expected identifier when parsing attribute name, got <eof>"
|
||||
);
|
||||
|
||||
result = tryParse(R"(@[
|
||||
function foo() end
|
||||
)");
|
||||
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors, 1, Location(Position(1, 8), Position(1, 16)), "Expected identifier when parsing attribute name, got 'function'"
|
||||
);
|
||||
|
||||
result = tryParse(R"(@[deprecated
|
||||
local function foo() end
|
||||
)");
|
||||
|
||||
checkFirstErrorForAttributes(
|
||||
result.errors, 1, Location(Position(1, 8), Position(1, 13)), "Expected ']' (to close '@[' at line 1), got 'local'"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_attribute_for_function_expression")
|
||||
{
|
||||
AstStatBlock* stat1 = parse(R"(
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTINT(LuauSolverConstraintLimit)
|
||||
LUAU_FASTINT(LuauTypeInferIterationLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
|
@ -28,6 +29,11 @@ LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
|
|||
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauLimitUnification)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
LUAU_FASTINT(LuauGenericCounterMaxDepth)
|
||||
|
||||
struct LimitFixture : BuiltinsFixture
|
||||
{
|
||||
|
@ -295,8 +301,8 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5))
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
|
||||
// And this flag is the one that fixes it.
|
||||
{FFlag::LuauSimplifyAnyAndUnion, true},
|
||||
|
@ -380,6 +386,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai
|
|||
{FFlag::LuauLimitDynamicConstraintSolving3, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
|
||||
{FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true},
|
||||
};
|
||||
|
@ -434,4 +441,179 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai
|
|||
CHECK(frontend->stats.dynamicConstraintsCreated < 40);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_should_cache_pairs_in_seen_set" * doctest::timeout(0.5))
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
// This flags surfaced and solves the problem. (The original PR was reverted)
|
||||
{FFlag::LuauSubtypingGenericsDoesntUseVariance, true},
|
||||
};
|
||||
|
||||
constexpr const char* src = R"LUAU(
|
||||
type DataProxy = any
|
||||
|
||||
type _Transaction = (c: _ApolloCache) -> ()
|
||||
type _ApolloCache = {
|
||||
read: <T, TVariables>(self: _ApolloCache, query: Cache_ReadOptions<TVariables, T>) -> T | nil,
|
||||
write: <TResult, TVariables>(self: _ApolloCache, write: Cache_WriteOptions<TResult, TVariables>) -> Reference | nil,
|
||||
diff: <T>(self: _ApolloCache, query: Cache_DiffOptions) -> Cache_DiffResult<T>,
|
||||
watch: (self: _ApolloCache, watch: Cache_WatchOptions<Record<string, any>>) -> (),
|
||||
reset: (self: _ApolloCache) -> Promise<nil>,
|
||||
evict: (self: _ApolloCache, options: Cache_EvictOptions) -> boolean,
|
||||
restore: (self: _ApolloCache, serializedState: TSerialized_) -> _ApolloCache,
|
||||
extract: (self: _ApolloCache, optimistic: boolean?) -> any,
|
||||
removeOptimistic: (self: _ApolloCache, id: string) -> (),
|
||||
batch: (self: _ApolloCache, options: Cache_BatchOptions<_ApolloCache>) -> (),
|
||||
performTransaction: (self: _ApolloCache, transaction: _Transaction, optimisticId: string) -> (),
|
||||
recordOptimisticTransaction: (self: _ApolloCache, transaction: _Transaction, optimisticId: string) -> (),
|
||||
transformDocument: (self: _ApolloCache, document: DocumentNode) -> DocumentNode,
|
||||
identify: (self: _ApolloCache, object: StoreObject | Reference) -> string | nil,
|
||||
gc: (self: _ApolloCache) -> Array<string>,
|
||||
modify: (self: _ApolloCache, options: Cache_ModifyOptions) -> boolean,
|
||||
transformForLink: (self: _ApolloCache, document: DocumentNode) -> DocumentNode,
|
||||
readQuery: <QueryType, TVariables>(
|
||||
self: _ApolloCache,
|
||||
options: Cache_ReadQueryOptions<QueryType, TVariables>,
|
||||
optimistic: boolean?
|
||||
) -> QueryType | nil,
|
||||
readFragment: <FragmentType, TVariables>(
|
||||
self: _ApolloCache,
|
||||
options: Cache_ReadFragmentOptions<FragmentType, TVariables>,
|
||||
optimistic: boolean?
|
||||
) -> FragmentType | nil,
|
||||
writeQuery: <TData, TVariables>(self: _ApolloCache, Cache_WriteQueryOptions<TData, TVariables>) -> Reference | nil,
|
||||
writeFragment: <TData, TVariables>(
|
||||
self: _ApolloCache,
|
||||
Cache_WriteFragmentOptions<TData, TVariables>
|
||||
) -> Reference | nil,
|
||||
}
|
||||
|
||||
export type ApolloCache<TSerialized> = {
|
||||
-- something here needed
|
||||
read: <T, TVariables>(self: ApolloCache<TSerialized>, query: Cache_ReadOptions<TVariables, T>) -> T | nil,
|
||||
write: <TResult, TVariables>(
|
||||
self: ApolloCache<TSerialized>,
|
||||
write: Cache_WriteOptions<TResult, TVariables>
|
||||
) -> Reference | nil,
|
||||
diff: <T>(self: ApolloCache<TSerialized>, query: Cache_DiffOptions) -> Cache_DiffResult<T>,
|
||||
watch: (self: ApolloCache<TSerialized>, watch: Cache_WatchOptions<Record<string, any>>) -> (() -> ()),
|
||||
reset: (self: ApolloCache<TSerialized>) -> Promise<nil>,
|
||||
evict: (self: ApolloCache<TSerialized>, options: Cache_EvictOptions) -> boolean,
|
||||
restore: (self: ApolloCache<TSerialized>, serializedState: TSerialized_) -> _ApolloCache,
|
||||
extract: (self: ApolloCache<TSerialized>, optimistic: boolean?) -> TSerialized,
|
||||
removeOptimistic: (self: ApolloCache<TSerialized>, id: string) -> (),
|
||||
batch: (self: ApolloCache<TSerialized>, options: Cache_BatchOptions<_ApolloCache>) -> (),
|
||||
performTransaction: (self: ApolloCache<TSerialized>, transaction: _Transaction, optimisticId: string) -> (),
|
||||
-- bottom text
|
||||
-- TOP
|
||||
recordOptimisticTransaction: (
|
||||
self: ApolloCache<TSerialized>,
|
||||
transaction: _Transaction,
|
||||
optimisticId: string
|
||||
) -> (),
|
||||
transformDocument: (self: ApolloCache<TSerialized>, document: DocumentNode) -> DocumentNode,
|
||||
identify: (self: ApolloCache<TSerialized>, object: StoreObject | Reference) -> string | nil,
|
||||
gc: (self: ApolloCache<TSerialized>) -> Array<string>,
|
||||
modify: (self: ApolloCache<TSerialized>, options: Cache_ModifyOptions) -> boolean,
|
||||
-- BOTTOM
|
||||
|
||||
transformForLink: (self: ApolloCache<TSerialized>, document: DocumentNode) -> DocumentNode,
|
||||
readQuery: <QueryType, TVariables>(
|
||||
self: ApolloCache<TSerialized>,
|
||||
options: Cache_ReadQueryOptions<QueryType, TVariables>,
|
||||
optimistic: boolean?
|
||||
) -> QueryType | nil,
|
||||
readFragment: <FragmentType, TVariables>(
|
||||
self: ApolloCache<TSerialized>,
|
||||
options: Cache_ReadFragmentOptions<FragmentType, TVariables>,
|
||||
optimistic: boolean?
|
||||
) -> FragmentType | nil,
|
||||
writeQuery: <TData, TVariables>(
|
||||
self: ApolloCache<TSerialized>,
|
||||
Cache_WriteQueryOptions<TData, TVariables>
|
||||
) -> Reference | nil,
|
||||
writeFragment: <TData, TVariables>(
|
||||
self: ApolloCache<TSerialized>,
|
||||
Cache_WriteFragmentOptions<TData, TVariables>
|
||||
) -> Reference | nil,
|
||||
}
|
||||
|
||||
|
||||
export type InMemoryCache = ApolloCache<NormalizedCacheObject> & {
|
||||
performTransaction: (
|
||||
self: InMemoryCache,
|
||||
update: (cache: InMemoryCache) -> ()
|
||||
) -> ()
|
||||
}
|
||||
|
||||
type InMemoryCachePrivate = InMemoryCache & {
|
||||
broadcastWatches: (self: InMemoryCachePrivate) -> (), -- ROBLOX NOTE: protected method
|
||||
}
|
||||
|
||||
local InMemoryCache = {}
|
||||
InMemoryCache.__index = InMemoryCache
|
||||
|
||||
-- InMemoryCache.batch = nil :: any
|
||||
function InMemoryCache:batch()
|
||||
self = self :: InMemoryCachePrivate
|
||||
|
||||
if self.txCount == 0 then
|
||||
self:broadcastWatches() -- problematic call?
|
||||
end
|
||||
end
|
||||
)LUAU";
|
||||
|
||||
std::ignore = check(src);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "test_generic_pruning_recursion_limit")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauReduceSetTypeStackPressure, true},
|
||||
};
|
||||
|
||||
ScopedFastInt sfi{FInt::LuauGenericCounterMaxDepth, 1};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
local function get(scale)
|
||||
print(scale.Do.Re.Mi)
|
||||
end
|
||||
)"));
|
||||
CHECK_EQ("<a>({ read Do: { read Re: { read Mi: a } } }) -> ()", toString(requireType("get")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterations_before_stopping" * doctest::timeout(2.0))
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
// These are necessary to trigger the bug
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
|
||||
// This is the fix
|
||||
{FFlag::LuauLimitUnification, true}
|
||||
};
|
||||
|
||||
ScopedFastInt sfi{FInt::LuauTypeInferIterationLimit, 100};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function l0<A...>()
|
||||
for l0=_,_ do
|
||||
end
|
||||
end
|
||||
|
||||
_ = if _._ then function(l0)
|
||||
end elseif _._G then if `` then {n0=_,} else "luauExprConstantSt" elseif _[_][l0] then function()
|
||||
end elseif _.n0 then if _[_] then if _ then _ else "aeld" elseif false then 0 else "lead"
|
||||
return _.n0
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR(result, UnificationTooComplex);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -195,6 +197,11 @@ struct SubtypeFixture : Fixture
|
|||
return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()});
|
||||
}
|
||||
|
||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy)
|
||||
{
|
||||
return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()});
|
||||
}
|
||||
|
||||
TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}});
|
||||
TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}});
|
||||
TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}});
|
||||
|
@ -724,11 +731,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> T <!: (number) -> string")
|
|||
CHECK_IS_NOT_SUBTYPE(genericTToTType, numberToStringType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(T) -> () <: <U>(U) -> ()")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () <!: <T>(T) -> ()")
|
||||
{
|
||||
CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType);
|
||||
|
@ -1430,6 +1432,94 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "(() -> number) -> () <: (<T>() -> T) -> ()")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
TypeId f1 = fn({nothingToNumberType}, {});
|
||||
TypeId f2 = fn({genericNothingToTType}, {});
|
||||
CHECK_IS_SUBTYPE(f1, f2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> ()) -> () <: (<T>(T) -> ()) -> ()")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
TypeId f1 = fn({numberToNothingType}, {});
|
||||
TypeId f2 = fn({genericTToNothingType}, {});
|
||||
CHECK_IS_SUBTYPE(f1, f2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> number) -> () <: (<T>(T) -> T) -> ()")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
TypeId f1 = fn({numberToNumberType}, {});
|
||||
TypeId f2 = fn({genericTToTType}, {});
|
||||
CHECK_IS_SUBTYPE(f1, f2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "<T>(x: T, y: T, f: (T, T) -> T) -> T <: (number, number, <U>(U, U) -> add<U, U>) -> number")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
TypeId f1 = arena.addType(FunctionType(
|
||||
{genericT},
|
||||
{},
|
||||
arena.addTypePack({genericT, genericT, fn({genericT, genericT}, {genericT})}),
|
||||
// (T, T, (T, T) -> T)
|
||||
arena.addTypePack({genericT}),
|
||||
std::nullopt,
|
||||
false
|
||||
));
|
||||
TypeId addUToU = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {genericU, genericU}});
|
||||
TypeId f2 = fn(
|
||||
{
|
||||
builtinTypes->numberType,
|
||||
builtinTypes->numberType,
|
||||
arena.addType(FunctionType({genericU}, {}, arena.addTypePack({genericU, genericU}), arena.addTypePack({addUToU})))
|
||||
// <U>(U, U) -> add<U, U>
|
||||
},
|
||||
{builtinTypes->numberType}
|
||||
);
|
||||
CHECK_IS_SUBTYPE(f1, f2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "no_caching_type_function_instances_with_mapped_generics")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
// (<U>(U) -> keyof<U>, <U>(U) -> keyof<U>) </: (({"a" : number}) -> "a", ({"b" : number}) -> "a")
|
||||
|
||||
TypeId keyOfU = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions.keyofFunc, {genericU}});
|
||||
// <U>(U) -> keyof<U>
|
||||
TypeId uToKeyOfU = arena.addType(FunctionType({genericU}, {}, arena.addTypePack({genericU}), arena.addTypePack({keyOfU})));
|
||||
TypePackId subTypePack = arena.addTypePack({uToKeyOfU, uToKeyOfU});
|
||||
|
||||
TypeId tblA = tbl({{"a", builtinTypes->numberType}});
|
||||
TypeId tblB = tbl({{"b", builtinTypes->numberType}});
|
||||
TypeId aSingleton = arena.addType(SingletonType{StringSingleton{"a"}});
|
||||
TypePackId superTypePack = arena.addTypePack({fn({tblA}, {aSingleton}), fn({tblB}, {aSingleton})});
|
||||
|
||||
CHECK_IS_NOT_SUBTYPE(subTypePack, superTypePack);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_non_generics_in_function_generics")
|
||||
{
|
||||
// This should not crash
|
||||
check(R"(
|
||||
local _ = _
|
||||
function _(l0)
|
||||
for _ in _(_) do
|
||||
end
|
||||
l0[_](
|
||||
_(_()) + _
|
||||
)
|
||||
end
|
||||
_(_)
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Subtyping.Subpaths");
|
||||
|
@ -1657,7 +1747,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
TypeId argTy = arena.freshType(getBuiltins(), moduleScope.get());
|
||||
|
|
|
@ -15,12 +15,13 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauEmptyStringInKeyOf)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauDoNotBlockOnStuckTypeFunctions)
|
||||
LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion)
|
||||
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
struct TypeFunctionFixture : Fixture
|
||||
{
|
||||
|
@ -161,8 +162,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function")
|
|||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
|
@ -732,6 +731,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1684,7 +1684,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_paramet
|
|||
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1712,6 +1713,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "undefined_add_application")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1731,8 +1733,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_prop
|
|||
if (!FFlag::LuauSolverV2)
|
||||
return;
|
||||
|
||||
ScopedFastFlag _{FFlag::LuauEmptyStringInKeyOf, true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foobar
|
||||
one: boolean
|
||||
|
@ -1770,9 +1770,10 @@ struct TFFixture
|
|||
TypeCheckLimits limits;
|
||||
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
|
||||
|
||||
const ScopedFastFlag sff[2] = {
|
||||
const ScopedFastFlag sff[3] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
BuiltinTypeFunctions builtinTypeFunctions;
|
||||
|
@ -1837,7 +1838,8 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
TypeId a = arena->addType(GenericType{"A"});
|
||||
|
@ -1859,7 +1861,8 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}});
|
||||
|
@ -1874,12 +1877,35 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck")
|
|||
CHECK(tfit->state == TypeFunctionInstanceState::Stuck);
|
||||
}
|
||||
|
||||
// We want to make sure that `t1 where t1 = refine<t1, unknown>` becomes `unknown`, not a cyclic type.
|
||||
TEST_CASE_FIXTURE(TFFixture, "reduce_degenerate_refinement")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineOccursCheckDirectRecursion, true},
|
||||
};
|
||||
|
||||
TypeId root = arena->addType(BlockedType{});
|
||||
TypeId refinement = arena->addType(TypeFunctionInstanceType{
|
||||
builtinTypeFunctions.refineFunc,
|
||||
{
|
||||
root,
|
||||
builtinTypes_.unknownType,
|
||||
}
|
||||
});
|
||||
|
||||
emplaceType<BoundType>(asMutable(root), refinement);
|
||||
reduceTypeFunctions(refinement, Location{}, tfc, true);
|
||||
CHECK_EQ("unknown", toString(refinement));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauForceSimplifyConstraint2, true},
|
||||
{FFlag::LuauDoNotBlockOnStuckTypeFunctions, true},
|
||||
};
|
||||
|
@ -1893,4 +1919,52 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or")
|
|||
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation")
|
||||
{
|
||||
ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type a<T> = {a<{T}>}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation1")
|
||||
{
|
||||
ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type b<T> = {b<T | string>}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation2")
|
||||
{
|
||||
ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type c<T> = {c<T & string>}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation3")
|
||||
{
|
||||
ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type d<T> = (d<T | string>) -> ()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauSolverV2)
|
|||
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
|
||||
|
||||
|
@ -2372,6 +2373,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -12,7 +12,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauTableCloneClonesType3)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling)
|
||||
LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
|
||||
|
@ -1715,7 +1715,6 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion")
|
|||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauWriteOnlyPropertyMangling, true},
|
||||
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
|
||||
};
|
||||
|
||||
|
@ -1736,6 +1735,23 @@ table.insert(1::any, 2::any)
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_requires_all_fields")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauNoScopeShallNotSubsumeAll, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function huh(): { { x: number, y: string } }
|
||||
local ret = {}
|
||||
while true do
|
||||
table.insert(ret, { x = 42 })
|
||||
end
|
||||
return ret
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_identity")
|
||||
{
|
||||
// This will not result in a real refinement, as we refine `bnot`, a function, to be truthy
|
||||
|
|
|
@ -901,7 +901,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "ice_while_checking_script_due_to_scopes_no
|
|||
// new solver code paths.
|
||||
// This is necessary to repro an ice that can occur in studio
|
||||
ScopedFastFlag luauSolverOff{FFlag::LuauSolverV2, false};
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
|
||||
getFrontend().setLuauSolverMode(SolverMode::New);
|
||||
ScopedFastFlag sff{FFlag::LuauScopeMethodsAreSolverAgnostic, true};
|
||||
|
||||
auto result = check(R"(
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
|
||||
TEST_SUITE_BEGIN("DefinitionTests");
|
||||
|
||||
|
@ -571,10 +570,7 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
};
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function middle(a: number, b: number): number
|
||||
|
|
|
@ -25,11 +25,13 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauCollapseShouldNotCrash)
|
||||
LUAU_FASTFLAG(LuauFormatUseLastPosition)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferFunctions");
|
||||
|
||||
|
@ -1972,7 +1974,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
|
|||
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_CHECK_NO_ERRORS(result);
|
||||
CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g")));
|
||||
if (!FFlag::LuauSubtypingGenericsDoesntUseVariance) // FIXME CLI-162439, the below fails on Linux with the flag on
|
||||
CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g")));
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
|
@ -2596,6 +2599,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
// CLI-114134: This test:
|
||||
|
@ -2623,7 +2627,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type")
|
|||
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2911,8 +2916,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function foo(player)
|
||||
local success,result = player:thing()
|
||||
|
@ -3327,4 +3330,28 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_should_not_crash")
|
|||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unnecessary_nil_in_lower_bound_of_generic")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true};
|
||||
|
||||
CheckResult result = check(
|
||||
Mode::Nonstrict,
|
||||
R"(
|
||||
function isAnArray(value)
|
||||
if type(value) == "table" then
|
||||
for index, _ in next, value do
|
||||
-- assert index is not nil
|
||||
math.max(0, index)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
)"
|
||||
);
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,12 +11,13 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauIntersectNotNil)
|
||||
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -782,8 +783,6 @@ TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names_old_solver")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type C = () -> ()
|
||||
type D = <T>() -> ()
|
||||
|
@ -815,8 +814,6 @@ local d: D = c
|
|||
}
|
||||
TEST_CASE_FIXTURE(Fixture, "generic_function_mismatch_with_argument")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type C = (number) -> ()
|
||||
type D = <T>(number) -> ()
|
||||
|
@ -849,8 +846,6 @@ local d: D = c
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type C = () -> ()
|
||||
type D = <T...>() -> ()
|
||||
|
@ -1414,8 +1409,6 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument")
|
||||
{
|
||||
|
||||
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -1500,14 +1493,14 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded"
|
|||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
// Important FIXME CLI-158432: This test exposes some problems with overload
|
||||
// Important FIXME CLI-161128: This test exposes some problems with overload
|
||||
// selection and generic type substitution when
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
||||
{
|
||||
ScopedFastFlag _[] = {
|
||||
{FFlag::LuauSubtypingCheckFunctionGenericCounts, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
CheckResult result;
|
||||
|
||||
|
@ -1546,6 +1539,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions_2")
|
||||
{
|
||||
ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type t = <a>(a, a, (a, a) -> a) -> a
|
||||
type u = (number, number, <X>(X, X) -> X) -> number
|
||||
|
||||
local foo = (nil :: any) :: t
|
||||
local bar : u = foo
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table")
|
||||
{
|
||||
|
@ -1829,9 +1836,9 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_packs_shouldnt_be_bound_to_themselves")
|
|||
{
|
||||
ScopedFastFlag flags[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauSubtypingCheckFunctionGenericCounts, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
||||
|
@ -1468,10 +1467,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineTablesWithReadType, true},
|
||||
};
|
||||
ScopedFastFlag sffs{FFlag::LuauSolverV2, true};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Player
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferLoops");
|
||||
|
||||
|
@ -182,8 +181,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next")
|
|||
}
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local n
|
||||
local s
|
||||
|
|
|
@ -17,6 +17,8 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
|||
LUAU_FASTFLAG(DebugLuauMagicTypes)
|
||||
LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3)
|
||||
LUAU_FASTINT(LuauSolverConstraintLimit)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
|
||||
LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -855,7 +857,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_types_are_scrubbed_from_module")
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::DebugLuauMagicTypes, true},
|
||||
{FFlag::LuauLimitDynamicConstraintSolving3, true}
|
||||
{FFlag::LuauLimitDynamicConstraintSolving3, true},
|
||||
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
|
||||
};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
|
@ -863,8 +866,9 @@ return function(): _luau_blocked_type return nil :: any end
|
|||
)";
|
||||
|
||||
CheckResult result = getFrontend().check("game/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<InternalError>(result.errors[0]));
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
|
||||
CHECK(get<InternalError>(result.errors[1]));
|
||||
CHECK("(...any) -> *error-type*" == toString(getFrontend().moduleResolver.getModule("game/A")->returnType));
|
||||
}
|
||||
|
||||
|
@ -873,7 +877,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_type_errors_are_only_reported_once"
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::DebugLuauMagicTypes, true},
|
||||
{FFlag::LuauLimitDynamicConstraintSolving3, true}
|
||||
{FFlag::LuauLimitDynamicConstraintSolving3, true},
|
||||
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
|
||||
};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
|
@ -881,8 +886,9 @@ return function(): { X: _luau_blocked_type, Y: _luau_blocked_type } return nil :
|
|||
)";
|
||||
|
||||
CheckResult result = getFrontend().check("game/A");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<InternalError>(result.errors[0]));
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK(get<ConstraintSolvingIncompleteError>(result.errors[0]));
|
||||
CHECK(get<InternalError>(result.errors[1]));
|
||||
CHECK("(...any) -> { X: *error-type*, Y: *error-type* }" == toString(getFrontend().moduleResolver.getModule("game/A")->returnType));
|
||||
}
|
||||
|
||||
|
@ -919,4 +925,60 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "scrub_unsealed_tables")
|
|||
LUAU_CHECK_ERROR(result, CannotExtendTable);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "invalid_local_alias_shouldnt_shadow_imported_type")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type bad<T> = {T}
|
||||
return {}
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local a_mod = require(game.A)
|
||||
type bad<T> = {bad<{T}>}
|
||||
type fine<T> = a_mod.bad<T>
|
||||
local f: fine<number>
|
||||
)";
|
||||
|
||||
CheckResult result = getFrontend().check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
|
||||
ModulePtr b = getFrontend().moduleResolver.getModule("game/B");
|
||||
REQUIRE(b != nullptr);
|
||||
std::optional<TypeId> fType = requireType(b, "f");
|
||||
REQUIRE(fType);
|
||||
// The important thing here is that it isn't *error-type*, since that would mean that the local definition of `bad` is shadowing the imported one
|
||||
CHECK(toString(*fType) == "fine<number>");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "invalid_alias_should_export_as_error_type")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauNameConstraintRestrictRecursiveTypes, true};
|
||||
|
||||
fileResolver.source["game/A"] = R"(
|
||||
export type bad<T> = {bad<{T}>}
|
||||
return {}
|
||||
)";
|
||||
|
||||
fileResolver.source["game/B"] = R"(
|
||||
local a_mod = require(game.A)
|
||||
local f: a_mod.bad<number>
|
||||
)";
|
||||
|
||||
CheckResult result = getFrontend().check("game/B");
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<RecursiveRestraintViolation>(result.errors[0]));
|
||||
|
||||
ModulePtr b = getFrontend().moduleResolver.getModule("game/B");
|
||||
REQUIRE(b != nullptr);
|
||||
std::optional<TypeId> fType = requireType(b, "f");
|
||||
REQUIRE(fType);
|
||||
if (FFlag::LuauSolverV2)
|
||||
CHECK(toString(*fType) == "*error-type*");
|
||||
else
|
||||
CHECK(toString(*fType) == "bad<number>");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -11,6 +11,7 @@ using namespace Luau;
|
|||
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -55,7 +56,8 @@ TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -15,6 +15,7 @@ using namespace Luau;
|
|||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOOP");
|
||||
|
||||
|
@ -556,6 +557,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -589,6 +591,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferOperators");
|
||||
|
@ -1443,7 +1444,11 @@ local function foo(arg: {name: string}?)
|
|||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// FIXME(CLI-165431): fixing subtyping revealed an overload selection problems
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array_simplified")
|
||||
|
|
|
@ -1352,4 +1352,27 @@ TEST_CASE_FIXTURE(Fixture, "loop_unsoundness")
|
|||
)"));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_and_test_two_props")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown): string
|
||||
if typeof(x) == 'table' then
|
||||
if typeof(x.foo) == 'string' and typeof(x.bar) == 'string' then
|
||||
return x.foo .. x.bar
|
||||
end
|
||||
end
|
||||
return ''
|
||||
end
|
||||
)");
|
||||
|
||||
// We'd like for this to be 0
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_MESSAGE(get<UnknownProperty>(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]);
|
||||
CHECK(Position{3, 56} == result.errors[0].location.begin);
|
||||
CHECK(Position{3, 61} == result.errors[0].location.end);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -13,12 +13,14 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification)
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
|
||||
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauRefineNoRefineAlways)
|
||||
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
|
||||
LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauRefineDistributesOverUnions)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions)
|
||||
LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -141,7 +143,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture
|
|||
|
||||
for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings)
|
||||
persist(ty.type);
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
|
||||
freeze(getFrontend().globals.globalTypes);
|
||||
}
|
||||
|
@ -464,6 +466,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_tested_n
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "call_to_undefined_method_is_not_a_refinement")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: unknown)
|
||||
if typeof(x) == "table" then
|
||||
if x.foo() then
|
||||
end
|
||||
end
|
||||
return (nil :: never)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
auto unknownProp = get<UnknownProperty>(result.errors[0]);
|
||||
REQUIRE_MESSAGE(unknownProp, "Expected UnknownProperty but got " << result.errors[0]);
|
||||
CHECK("foo" == unknownProp->key);
|
||||
CHECK("table" == toString(unknownProp->table));
|
||||
|
||||
CHECK(Position{3, 19} == result.errors[0].location.begin);
|
||||
CHECK(Position{3, 24} == result.errors[0].location.end);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_typeguard")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -512,8 +542,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: number?} = {x = 1}
|
||||
|
||||
|
@ -658,6 +686,7 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauNormalizationReorderFreeTypeIntersect, true},
|
||||
};
|
||||
|
||||
|
@ -1017,8 +1046,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function f(t: {x: boolean}?)
|
||||
if not t or t.x then
|
||||
|
@ -1271,8 +1298,6 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = {tag: "missing", x: nil} | {tag: "exists", x: string}
|
||||
|
||||
|
@ -1438,7 +1463,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vec
|
|||
{
|
||||
// CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
local function f(vec)
|
||||
local X, Y, Z = vec.X, vec.Y, vec.Z
|
||||
|
@ -2229,6 +2254,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction"
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauForceSimplifyConstraint2, true},
|
||||
};
|
||||
|
||||
|
@ -2250,6 +2276,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction"
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant")
|
||||
{
|
||||
ScopedFastFlag _[] = {
|
||||
{FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true},
|
||||
{FFlag::LuauNormalizationReorderFreeTypeIntersect, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
};
|
||||
|
||||
// FIXME CLI-141364: An underlying bug in normalization means the type of
|
||||
// `isIndexKey` is platform dependent.
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2260,7 +2294,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_
|
|||
and math.floor(k) == k -- no float keys
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
// For some reason we emit two errors here.
|
||||
for (const auto& e : result.errors)
|
||||
CHECK(get<ExplicitFunctionAnnotationRecommended>(e));
|
||||
}
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "ex")
|
||||
|
@ -2686,10 +2728,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
|
|||
|
||||
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineTablesWithReadType, true},
|
||||
};
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function invokeDisconnect(d: unknown)
|
||||
|
@ -2705,10 +2744,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single")
|
|||
|
||||
TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_union")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineTablesWithReadType, true},
|
||||
};
|
||||
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type Disconnectable = {
|
||||
|
@ -2876,12 +2912,41 @@ TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node")
|
|||
)"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_from_and_should_not_refine_to_never")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineDistributesOverUnions, true},
|
||||
};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare extern type Config with
|
||||
KeyboardEnabled: boolean
|
||||
MouseEnabled: boolean
|
||||
end
|
||||
)");
|
||||
|
||||
CheckResult results = check(R"(
|
||||
local config: Config
|
||||
local function serialize()
|
||||
if config.KeyboardEnabled and config.MouseEnabled then
|
||||
return 0
|
||||
else
|
||||
print(config)
|
||||
return 1
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(results);
|
||||
CHECK_EQ("Config", toString(requireTypeAtPosition({6, 24})));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauForceSimplifyConstraint2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
|
|
|
@ -23,18 +23,18 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
|||
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit)
|
||||
LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint)
|
||||
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
|
||||
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
|
||||
LUAU_FASTFLAG(LuauSolverAgnosticStringification)
|
||||
LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
|
||||
LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
|
||||
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds)
|
||||
LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance)
|
||||
|
||||
TEST_SUITE_BEGIN("TableTests");
|
||||
|
||||
|
@ -762,8 +762,6 @@ TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = {"one", "two", "three"}
|
||||
)");
|
||||
|
@ -2053,8 +2051,6 @@ TEST_CASE_FIXTURE(Fixture, "explicit_nil_indexer")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a: string | number = 1
|
||||
local t = {a, 1}
|
||||
|
@ -2359,6 +2355,7 @@ local t: { a: {Foo}, b: number } = {
|
|||
// since mutating properties means table properties should be invariant.
|
||||
TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauSubtypingGenericsDoesntUseVariance, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
|
@ -2371,24 +2368,12 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
|
|||
local c : string = t.m("hi")
|
||||
)");
|
||||
|
||||
if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)
|
||||
if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_CHECK_ERROR_COUNT(1, result);
|
||||
LUAU_CHECK_ERROR_COUNT(2, result);
|
||||
LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended);
|
||||
LUAU_CHECK_ERROR(result, TypeMismatch);
|
||||
}
|
||||
else if (FFlag::LuauSolverV2)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(get<ExplicitFunctionAnnotationRecommended>(result.errors[0]));
|
||||
|
||||
// This is not actually the expected behavior, but the typemismatch we were seeing before was for the wrong reason.
|
||||
// The behavior of this test is just regressed generally in the new solver, and will need to be consciously addressed.
|
||||
}
|
||||
|
||||
// TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping
|
||||
else if (FFlag::LuauInstantiateInSubtyping)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
@ -2416,7 +2401,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope
|
|||
table.insert(buttons, { a = 3 })
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// FIXME(CLI-165431): fixing subtyping revealed an overload selection problems
|
||||
if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll)
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_detailed_prop")
|
||||
|
@ -3171,8 +3160,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_quantify_table_that_belongs_to_outer_sc
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_tables_at_scope_level")
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local Option = {}
|
||||
|
@ -3514,7 +3501,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_tabl
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t: {x: number?}? = {x = nil}
|
||||
|
@ -3746,7 +3732,8 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -4626,6 +4613,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -5017,8 +5005,6 @@ end
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local test = if true then { "meow", "woof" } else { 4, 81 }
|
||||
local test2 = test[1]
|
||||
|
@ -5786,11 +5772,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1859")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1797_intersection_of_tables_arent_disjoint")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRelateTablesAreNeverDisjoint, true},
|
||||
{FFlag::LuauRefineTablesWithReadType, true},
|
||||
};
|
||||
ScopedFastFlag sffs{FFlag::LuauSolverV2, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
--!strict
|
||||
|
@ -5819,8 +5801,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1797_intersection_of_tables_arent_disjoi
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1344")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
--!strict
|
||||
type t = {
|
||||
|
@ -5843,8 +5823,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1344")
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1651")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
--!strict
|
||||
local MyModule = {}
|
||||
|
@ -5912,7 +5890,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
|
@ -5949,8 +5928,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauDfgForwardNilFromAndOr, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
export type CachedValue<T> = {
|
||||
future: any,
|
||||
|
@ -6034,10 +6011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assert
|
|||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_normalization" * doctest::timeout(1.0))
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
{FFlag::LuauNormalizationLimitTyvarUnionSize, true},
|
||||
};
|
||||
ScopedFastFlag sff{FFlag::LuauNormalizationLimitTyvarUnionSize, true};
|
||||
|
||||
const std::string source =
|
||||
"local res = {\n" + rep("\"foo\",\n", 100) + "}\n"
|
||||
|
@ -6050,5 +6024,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_norma
|
|||
LUAU_REQUIRE_NO_ERRORS(check(source));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "free_types_with_sealed_table_upper_bounds_can_still_be_expanded")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauExtendSealedTableUpperBounds, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function bar(a: {x: number}) end
|
||||
|
||||
function foo(a)
|
||||
bar(a)
|
||||
|
||||
-- Here, a : A where A = never <: A <: {x: number}
|
||||
-- The upper bound of A is a sealed table, but we nevertheless want to extend it.
|
||||
a.nope()
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("({ read nope: () -> (...unknown) } & { x: number }) -> ()" == toString(requireType("foo")));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -25,11 +25,7 @@ LUAU_FASTINT(LuauRecursionLimit)
|
|||
LUAU_FASTINT(LuauTypeInferTypePackLoopLimit)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
|
||||
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
|
||||
LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature)
|
||||
LUAU_FASTFLAG(LuauOccursCheckForRefinement)
|
||||
LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties)
|
||||
LUAU_FASTFLAG(LuauInferActualIfElseExprType2)
|
||||
LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
|
||||
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
|
||||
|
@ -38,6 +34,7 @@ LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
|
|||
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
|
||||
LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -615,7 +612,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
{
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local t = { x = 10, y = 20 }
|
||||
|
@ -626,7 +623,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
export type = number
|
||||
|
@ -638,7 +635,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
|
||||
{
|
||||
DOES_NOT_PASS_NEW_SOLVER_GUARD();
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
function string.() end
|
||||
|
@ -648,7 +645,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local function () end
|
||||
|
@ -659,7 +656,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_
|
|||
}
|
||||
|
||||
{
|
||||
getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old);
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
local dm = {}
|
||||
|
@ -2019,6 +2016,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
auto result = check(R"(
|
||||
|
@ -2055,6 +2053,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2089,6 +2088,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
|
@ -2173,8 +2173,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_table_indexer" * doctest::timeout(0.5))
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
_[_] += true
|
||||
_ = {
|
||||
|
@ -2368,7 +2366,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
|
@ -2406,7 +2405,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_occurs_check_stack_overflow")
|
||||
{
|
||||
ScopedFastFlag _{FFlag::LuauOccursCheckForRefinement, true};
|
||||
// We just want this to not stack overflow, it's ok for it to barf errors.
|
||||
LUAU_REQUIRE_ERRORS(check(R"(
|
||||
_ = if _ then _
|
||||
|
@ -2418,10 +2416,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_occurs_check_stack_overflow")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "fuzzer_infer_divergent_rw_props")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauInferPolarityOfReadWriteProperties, true},
|
||||
};
|
||||
ScopedFastFlag sffs{FFlag::LuauSolverV2, true};
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(check(R"(
|
||||
return function(l0:{_:(any)&(any),write _:any,})
|
||||
|
@ -2445,9 +2440,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim")
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauInferActualIfElseExprType2, true},
|
||||
// This is needed so that we don't hide the string literal free types
|
||||
// behind a `union<_, _>`
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
|
@ -2526,7 +2518,6 @@ TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force")
|
|||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauForceSimplifyConstraint2, true},
|
||||
{FFlag::LuauSimplifyOutOfLine2, true},
|
||||
// NOTE: Feel free to clip this test when this flag is clipped.
|
||||
{FFlag::LuauPushFunctionTypesInFunctionStatement, false},
|
||||
};
|
||||
|
@ -2558,6 +2549,9 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden")
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::DebugLuauMagicTypes, true},
|
||||
{FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true},
|
||||
// This debug flag is normally on, but we turn it off as we're testing
|
||||
// the exact behavior it enables.
|
||||
{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false},
|
||||
};
|
||||
|
||||
CheckResult results = check(R"(
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
|
||||
LUAU_FASTFLAG(LuauSolverV2)
|
||||
LUAU_FASTFLAG(LuauEagerGeneralization4)
|
||||
LUAU_FASTFLAG(LuauRefineDistributesOverUnions)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
LUAU_FASTFLAG(LuauReduceSetTypeStackPressure)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
|
@ -361,6 +364,12 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type"
|
|||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_2")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauRefineDistributesOverUnions, true},
|
||||
{FFlag::LuauReduceSetTypeStackPressure, true},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local t = {x = nil}
|
||||
|
||||
|
@ -375,10 +384,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_
|
|||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK_EQ("t | { x: number }", toString(err->wantedType));
|
||||
CHECK_EQ("{ x: string }", toString(err->givenType));
|
||||
|
||||
CHECK("{ x: nil } | { x: number }" == toString(requireTypeAtPosition({4, 18}), {true}));
|
||||
CHECK_EQ("number?", toString(err->wantedType));
|
||||
CHECK_EQ("string", toString(err->givenType));
|
||||
CHECK("{ x: number? }" == toString(requireTypeAtPosition({4, 18}), {true}));
|
||||
CHECK("number?" == toString(requireTypeAtPosition({4, 20})));
|
||||
}
|
||||
|
||||
|
@ -403,6 +411,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur
|
|||
{FFlag::LuauSolverV2, true},
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -10,6 +10,7 @@ LUAU_FASTFLAG(LuauSolverV2);
|
|||
LUAU_FASTFLAG(LuauEagerGeneralization4);
|
||||
LUAU_FASTFLAG(LuauForceSimplifyConstraint2)
|
||||
LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks)
|
||||
LUAU_FASTFLAG(LuauResetConditionalContextProperly)
|
||||
|
||||
TEST_SUITE_BEGIN("TypeInferUnknownNever");
|
||||
|
||||
|
@ -333,6 +334,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i
|
|||
ScopedFastFlag sffs[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true},
|
||||
{FFlag::LuauForceSimplifyConstraint2, true},
|
||||
};
|
||||
|
||||
|
@ -360,7 +362,8 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never")
|
|||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauEagerGeneralization4, true},
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true}
|
||||
{FFlag::LuauTrackFreeInteriorTypePacks, true},
|
||||
{FFlag::LuauResetConditionalContextProperly, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
|
|
|
@ -53,7 +53,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: number")
|
|||
{
|
||||
auto [left, freeLeft] = freshType();
|
||||
|
||||
CHECK(u2.unify(left, builtinTypes.numberType));
|
||||
CHECK(UnifyResult::Ok == u2.unify(left, builtinTypes.numberType));
|
||||
|
||||
CHECK("never" == toString(freeLeft->lowerBound));
|
||||
CHECK("number" == toString(freeLeft->upperBound));
|
||||
|
@ -63,7 +63,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "number <: T")
|
|||
{
|
||||
auto [right, freeRight] = freshType();
|
||||
|
||||
CHECK(u2.unify(builtinTypes.numberType, right));
|
||||
CHECK(UnifyResult::Ok == u2.unify(builtinTypes.numberType, right));
|
||||
|
||||
CHECK("number" == toString(freeRight->lowerBound));
|
||||
CHECK("unknown" == toString(freeRight->upperBound));
|
||||
|
@ -74,7 +74,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
|||
auto [left, freeLeft] = freshType();
|
||||
auto [right, freeRight] = freshType();
|
||||
|
||||
CHECK(u2.unify(left, right));
|
||||
CHECK(UnifyResult::Ok == u2.unify(left, right));
|
||||
|
||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left));
|
||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right));
|
||||
|
|
|
@ -379,7 +379,7 @@ local function vec3compsum(a: vector)
|
|||
return a.X + a.Y + a.Z
|
||||
end
|
||||
|
||||
assert(vec3compsum(vector(1, 2, 4)) == 7.0)
|
||||
assert(vec3compsum(vector.create(1, 2, 4)) == 7.0)
|
||||
|
||||
local function vec3add(a: vector, b: vector) return a + b end
|
||||
local function vec3sub(a: vector, b: vector) return a - b end
|
||||
|
@ -387,17 +387,17 @@ local function vec3mul(a: vector, b: vector) return a * b end
|
|||
local function vec3div(a: vector, b: vector) return a / b end
|
||||
local function vec3neg(a: vector) return -a end
|
||||
|
||||
assert(vec3add(vector(10, 20, 40), vector(1, 0, 2)) == vector(11, 20, 42))
|
||||
assert(vec3sub(vector(10, 20, 40), vector(1, 0, 2)) == vector(9, 20, 38))
|
||||
assert(vec3mul(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, 0, 80))
|
||||
assert(vec3div(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, math.huge, 20))
|
||||
assert(vec3neg(vector(10, 20, 40)) == vector(-10, -20, -40))
|
||||
assert(vec3add(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(11, 20, 42))
|
||||
assert(vec3sub(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(9, 20, 38))
|
||||
assert(vec3mul(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(10, 0, 80))
|
||||
assert(vec3div(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(10, math.huge, 20))
|
||||
assert(vec3neg(vector.create(10, 20, 40)) == vector.create(-10, -20, -40))
|
||||
|
||||
local function vec3mulnum(a: vector, b: number) return a * b end
|
||||
local function vec3mulconst(a: vector) return a * 4 end
|
||||
|
||||
assert(vec3mulnum(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
||||
assert(vec3mulconst(vector(10, 20, 40), 4) == vector(40, 80, 160))
|
||||
assert(vec3mulnum(vector.create(10, 20, 40), 4) == vector.create(40, 80, 160))
|
||||
assert(vec3mulconst(vector.create(10, 20, 40), 4) == vector.create(40, 80, 160))
|
||||
|
||||
local function bufferbounds(zero)
|
||||
local b1 = buffer.create(1)
|
||||
|
|
|
@ -67,7 +67,7 @@ ecall(checkthread, 2)
|
|||
call(checkuserdata, newproxy())
|
||||
ecall(checkuserdata, 2)
|
||||
|
||||
call(checkvector, vector(1, 2, 3))
|
||||
call(checkvector, vector.create(1, 2, 3))
|
||||
ecall(checkvector, 2)
|
||||
|
||||
call(checkbuffer, buffer.create(10))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
print('testing vectors')
|
||||
|
||||
-- detect vector size
|
||||
local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3
|
||||
local vector_size = if pcall(function() return vector.create(0, 0, 0).w end) then 4 else 3
|
||||
|
||||
function ecall(fn, ...)
|
||||
local ok, err = pcall(fn, ...)
|
||||
|
@ -11,99 +11,99 @@ function ecall(fn, ...)
|
|||
end
|
||||
|
||||
-- equality
|
||||
assert(vector(1, 2, 3) == vector(1, 2, 3))
|
||||
assert(vector(0, 1, 2) == vector(-0, 1, 2))
|
||||
assert(vector(1, 2, 3) ~= vector(1, 2, 4))
|
||||
assert(vector.create(1, 2, 3) == vector.create(1, 2, 3))
|
||||
assert(vector.create(0, 1, 2) == vector.create(-0, 1, 2))
|
||||
assert(vector.create(1, 2, 3) ~= vector.create(1, 2, 4))
|
||||
|
||||
-- rawequal
|
||||
assert(rawequal(vector(1, 2, 3), vector(1, 2, 3)))
|
||||
assert(rawequal(vector(0, 1, 2), vector(-0, 1, 2)))
|
||||
assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4)))
|
||||
assert(rawequal(vector.create(1, 2, 3), vector.create(1, 2, 3)))
|
||||
assert(rawequal(vector.create(0, 1, 2), vector.create(-0, 1, 2)))
|
||||
assert(not rawequal(vector.create(1, 2, 3), vector.create(1, 2, 4)))
|
||||
|
||||
-- type & tostring
|
||||
assert(type(vector(1, 2, 3)) == "vector")
|
||||
assert(type(vector.create(1, 2, 3)) == "vector")
|
||||
|
||||
if vector_size == 4 then
|
||||
assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4")
|
||||
assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0")
|
||||
assert(tostring(vector.create(1, 2, 3, 4)) == "1, 2, 3, 4")
|
||||
assert(tostring(vector.create(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0")
|
||||
else
|
||||
assert(tostring(vector(1, 2, 3)) == "1, 2, 3")
|
||||
assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5")
|
||||
assert(tostring(vector.create(1, 2, 3)) == "1, 2, 3")
|
||||
assert(tostring(vector.create(-1, 2, 0.5)) == "-1, 2, 0.5")
|
||||
end
|
||||
|
||||
local t = {}
|
||||
|
||||
-- basic table access
|
||||
t[vector(1, 2, 3)] = 42
|
||||
assert(t[vector(1, 2, 3)] == 42)
|
||||
assert(t[vector(1, 2, 4)] == nil)
|
||||
t[vector.create(1, 2, 3)] = 42
|
||||
assert(t[vector.create(1, 2, 3)] == 42)
|
||||
assert(t[vector.create(1, 2, 4)] == nil)
|
||||
|
||||
-- negative zero should hash the same as zero
|
||||
assert(t[vector(0, 0, 0)] == nil)
|
||||
t[vector(0, 0, 0)] = "hello"
|
||||
assert(t[vector(0, 0, 0)] == "hello")
|
||||
assert(t[vector(0, -0, 0)] == "hello")
|
||||
assert(t[vector.create(0, 0, 0)] == nil)
|
||||
t[vector.create(0, 0, 0)] = "hello"
|
||||
assert(t[vector.create(0, 0, 0)] == "hello")
|
||||
assert(t[vector.create(0, -0, 0)] == "hello")
|
||||
|
||||
-- test arithmetic instructions
|
||||
assert(vector(1, 2, 4) + vector(8, 16, 24) == vector(9, 18, 28));
|
||||
assert(vector(1, 2, 4) - vector(8, 16, 24) == vector(-7, -14, -20));
|
||||
assert(vector.create(1, 2, 4) + vector.create(8, 16, 24) == vector.create(9, 18, 28));
|
||||
assert(vector.create(1, 2, 4) - vector.create(8, 16, 24) == vector.create(-7, -14, -20));
|
||||
|
||||
local val = 1/'8'
|
||||
|
||||
assert(vector(1, 2, 4) * vector(8, 16, 24) == vector(8, 32, 96));
|
||||
assert(vector(1, 2, 4) * 8 == vector(8, 16, 32));
|
||||
assert(vector(1, 2, 4) * (1 / val) == vector(8, 16, 32));
|
||||
assert(8 * vector(8, 16, 24) == vector(64, 128, 192));
|
||||
assert(vector(1, 2, 4) * '8' == vector(8, 16, 32));
|
||||
assert('8' * vector(8, 16, 24) == vector(64, 128, 192));
|
||||
assert(vector.create(1, 2, 4) * vector.create(8, 16, 24) == vector.create(8, 32, 96));
|
||||
assert(vector.create(1, 2, 4) * 8 == vector.create(8, 16, 32));
|
||||
assert(vector.create(1, 2, 4) * (1 / val) == vector.create(8, 16, 32));
|
||||
assert(8 * vector.create(8, 16, 24) == vector.create(64, 128, 192));
|
||||
assert(vector.create(1, 2, 4) * '8' == vector.create(8, 16, 32));
|
||||
assert('8' * vector.create(8, 16, 24) == vector.create(64, 128, 192));
|
||||
|
||||
assert(vector(1, 2, 4) * -0.125 == vector(-0.125, -0.25, -0.5))
|
||||
assert(-0.125 * vector(1, 2, 4) == vector(-0.125, -0.25, -0.5))
|
||||
assert(vector.create(1, 2, 4) * -0.125 == vector.create(-0.125, -0.25, -0.5))
|
||||
assert(-0.125 * vector.create(1, 2, 4) == vector.create(-0.125, -0.25, -0.5))
|
||||
|
||||
assert(vector(1, 2, 4) * 100 == vector(100, 200, 400))
|
||||
assert(100 * vector(1, 2, 4) == vector(100, 200, 400))
|
||||
assert(vector.create(1, 2, 4) * 100 == vector.create(100, 200, 400))
|
||||
assert(100 * vector.create(1, 2, 4) == vector.create(100, 200, 400))
|
||||
|
||||
if vector_size == 4 then
|
||||
assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32));
|
||||
assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
|
||||
assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4));
|
||||
assert(vector.create(1, 2, 4, 8) / vector.create(8, 16, 24, 32) == vector.create(1/8, 2/16, 4/24, 8/32));
|
||||
assert(8 / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4));
|
||||
assert('8' / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4));
|
||||
else
|
||||
assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24));
|
||||
assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3));
|
||||
assert(vector.create(1, 2, 4) / vector.create(8, 16, 24, 1) == vector.create(1/8, 2/16, 4/24));
|
||||
assert(8 / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3));
|
||||
assert('8' / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3));
|
||||
end
|
||||
|
||||
assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2));
|
||||
assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8));
|
||||
assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2));
|
||||
assert(vector.create(1, 2, 4) / 8 == vector.create(1/8, 1/4, 1/2));
|
||||
assert(vector.create(1, 2, 4) / (1 / val) == vector.create(1/8, 2/8, 4/8));
|
||||
assert(vector.create(1, 2, 4) / '8' == vector.create(1/8, 1/4, 1/2));
|
||||
|
||||
assert(-vector(1, 2, 4) == vector(-1, -2, -4));
|
||||
assert(-vector.create(1, 2, 4) == vector.create(-1, -2, -4));
|
||||
|
||||
-- test floor division
|
||||
assert(vector(1, 3, 5) // 2 == vector(0, 1, 2))
|
||||
assert(vector(1, 3, 5) // val == vector(8, 24, 40))
|
||||
assert(vector.create(1, 3, 5) // 2 == vector.create(0, 1, 2))
|
||||
assert(vector.create(1, 3, 5) // val == vector.create(8, 24, 40))
|
||||
|
||||
if vector_size == 4 then
|
||||
assert(10 // vector(1, 2, 3, 4) == vector(10, 5, 3, 2))
|
||||
assert(vector(10, 9, 8, 7) // vector(1, 2, 3, 4) == vector(10, 4, 2, 1))
|
||||
assert(10 // vector.create(1, 2, 3, 4) == vector.create(10, 5, 3, 2))
|
||||
assert(vector.create(10, 9, 8, 7) // vector.create(1, 2, 3, 4) == vector.create(10, 4, 2, 1))
|
||||
else
|
||||
assert(10 // vector(1, 2, 3) == vector(10, 5, 3))
|
||||
assert(vector(10, 9, 8) // vector(1, 2, 3) == vector(10, 4, 2))
|
||||
assert(10 // vector.create(1, 2, 3) == vector.create(10, 5, 3))
|
||||
assert(vector.create(10, 9, 8) // vector.create(1, 2, 3) == vector.create(10, 4, 2))
|
||||
end
|
||||
|
||||
-- test NaN comparison
|
||||
local nanv = vector(0/0, 0/0, 0/0)
|
||||
local nanv = vector.create(0/0, 0/0, 0/0)
|
||||
assert(nanv ~= nanv);
|
||||
|
||||
-- __index
|
||||
assert(vector(1, 2, 2).Magnitude == 3)
|
||||
assert(vector(0, 0, 0)['Dot'](vector(1, 2, 4), vector(5, 6, 7)) == 45)
|
||||
assert(vector(2, 0, 0).Unit == vector(1, 0, 0))
|
||||
assert(vector.create(1, 2, 2).Magnitude == 3)
|
||||
assert(vector.create(0, 0, 0)['Dot'](vector.create(1, 2, 4), vector.create(5, 6, 7)) == 45)
|
||||
assert(vector.create(2, 0, 0).Unit == vector.create(1, 0, 0))
|
||||
|
||||
-- __namecall
|
||||
assert(vector(1, 2, 4):Dot(vector(5, 6, 7)) == 45)
|
||||
assert(ecall(function() vector(1, 2, 4):Dot() end) == "missing argument #2 (vector expected)")
|
||||
assert(ecall(function() vector(1, 2, 4):Dot("a") end) == "invalid argument #2 (vector expected, got string)")
|
||||
assert(vector.create(1, 2, 4):Dot(vector.create(5, 6, 7)) == 45)
|
||||
assert(ecall(function() vector.create(1, 2, 4):Dot() end) == "missing argument #2 (vector expected)")
|
||||
assert(ecall(function() vector.create(1, 2, 4):Dot("a") end) == "invalid argument #2 (vector expected, got string)")
|
||||
|
||||
local function doDot1(a: vector, b)
|
||||
return a:Dot(b)
|
||||
|
@ -113,39 +113,39 @@ local function doDot2(a: vector, b)
|
|||
return (a:Dot(b))
|
||||
end
|
||||
|
||||
local v124 = vector(1, 2, 4)
|
||||
local v124 = vector.create(1, 2, 4)
|
||||
|
||||
assert(doDot1(v124, vector(5, 6, 7)) == 45)
|
||||
assert(doDot2(v124, vector(5, 6, 7)) == 45)
|
||||
assert(doDot1(v124, vector.create(5, 6, 7)) == 45)
|
||||
assert(doDot2(v124, vector.create(5, 6, 7)) == 45)
|
||||
assert(ecall(function() doDot1(v124, "a") end) == "invalid argument #2 (vector expected, got string)")
|
||||
assert(ecall(function() doDot2(v124, "a") end) == "invalid argument #2 (vector expected, got string)")
|
||||
assert(select("#", doDot1(v124, vector(5, 6, 7))) == 1)
|
||||
assert(select("#", doDot2(v124, vector(5, 6, 7))) == 1)
|
||||
assert(select("#", doDot1(v124, vector.create(5, 6, 7))) == 1)
|
||||
assert(select("#", doDot2(v124, vector.create(5, 6, 7))) == 1)
|
||||
|
||||
-- can't use vector with NaN components as table key
|
||||
assert(pcall(function() local t = {} t[vector(0/0, 2, 3)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} t[vector(1, 0/0, 3)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} t[vector(1, 2, 0/0)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == false)
|
||||
assert(pcall(function() local t = {} t[vector.create(0/0, 2, 3)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} t[vector.create(1, 0/0, 3)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} t[vector.create(1, 2, 0/0)] = 1 end) == false)
|
||||
assert(pcall(function() local t = {} rawset(t, vector.create(0/0, 2, 3), 1) end) == false)
|
||||
|
||||
assert(vector(1, 0, 0):Cross(vector(0, 1, 0)) == vector(0, 0, 1))
|
||||
assert(vector(0, 1, 0):Cross(vector(1, 0, 0)) == vector(0, 0, -1))
|
||||
assert(vector.create(1, 0, 0):Cross(vector.create(0, 1, 0)) == vector.create(0, 0, 1))
|
||||
assert(vector.create(0, 1, 0):Cross(vector.create(1, 0, 0)) == vector.create(0, 0, -1))
|
||||
|
||||
-- make sure we cover both builtin and C impl
|
||||
assert(vector(1, 2, 4) == vector("1", "2", "4"))
|
||||
assert(vector.create(1, 2, 4) == vector.create("1", "2", "4"))
|
||||
|
||||
-- validate component access (both cases)
|
||||
assert(vector(1, 2, 3).x == 1)
|
||||
assert(vector(1, 2, 3).X == 1)
|
||||
assert(vector(1, 2, 3).y == 2)
|
||||
assert(vector(1, 2, 3).Y == 2)
|
||||
assert(vector(1, 2, 3).z == 3)
|
||||
assert(vector(1, 2, 3).Z == 3)
|
||||
assert(vector.create(1, 2, 3).x == 1)
|
||||
assert(vector.create(1, 2, 3).X == 1)
|
||||
assert(vector.create(1, 2, 3).y == 2)
|
||||
assert(vector.create(1, 2, 3).Y == 2)
|
||||
assert(vector.create(1, 2, 3).z == 3)
|
||||
assert(vector.create(1, 2, 3).Z == 3)
|
||||
|
||||
-- additional checks for 4-component vectors
|
||||
if vector_size == 4 then
|
||||
assert(vector(1, 2, 3, 4).w == 4)
|
||||
assert(vector(1, 2, 3, 4).W == 4)
|
||||
assert(vector.create(1, 2, 3, 4).w == 4)
|
||||
assert(vector.create(1, 2, 3, 4).W == 4)
|
||||
end
|
||||
|
||||
-- negative zero should hash the same as zero
|
||||
|
@ -153,15 +153,15 @@ end
|
|||
do
|
||||
local larget = {}
|
||||
for i = 1, 2^14 do
|
||||
larget[vector(0, 0, i)] = true
|
||||
larget[vector.create(0, 0, i)] = true
|
||||
end
|
||||
|
||||
larget[vector(0, 0, 0)] = 42
|
||||
larget[vector.create(0, 0, 0)] = 42
|
||||
|
||||
assert(larget[vector(0, 0, 0)] == 42)
|
||||
assert(larget[vector(0, 0, -0)] == 42)
|
||||
assert(larget[vector(0, -0, 0)] == 42)
|
||||
assert(larget[vector(-0, 0, 0)] == 42)
|
||||
assert(larget[vector.create(0, 0, 0)] == 42)
|
||||
assert(larget[vector.create(0, 0, -0)] == 42)
|
||||
assert(larget[vector.create(0, -0, 0)] == 42)
|
||||
assert(larget[vector.create(-0, 0, 0)] == 42)
|
||||
end
|
||||
|
||||
local function numvectemporary()
|
||||
|
@ -174,7 +174,7 @@ local function numvectemporary()
|
|||
return tmp, num2
|
||||
end
|
||||
|
||||
local a, b = proptab.vec3compsum(vector(2, 6, 0))
|
||||
local a, b = proptab.vec3compsum(vector.create(2, 6, 0))
|
||||
|
||||
assert(a.X == 0.25)
|
||||
assert(a.Y == 0.75)
|
||||
|
|
Loading…
Add table
Reference in a new issue