Sync to upstream/release/684 (#1930)

## General
- Support AstStatDeclareGlobal output as a source string (via
@karl-police in #1889)
- Luau heap dump correctly reports the size of a string, now including
overhead for the string type
- Prevent yields from Luau `xpcall` error handling function.
 
## Analysis
- Avoid exponential blowup when normalizing union of normalized free
variables.
- Fix type pack-related bugs that caused infinite recursion when:
  - A generic type pack was bound to itself during subtyping.
- In type pack flattening, when that same generic type pack was now
being bound another generic type pack which contained it.
- Properly simplify `any & (*error-type* | string)` to `*error-type* |
*error-type* | string` instead of hanging due to creating a huge union
type.

---

Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
ayoungbloodrbx 2025-07-25 15:33:42 -07:00 committed by GitHub
parent b668ffb8c8
commit 66202dc4ac
Signed by: DevComp
GPG key ID: B5690EEEBB952194
87 changed files with 1103 additions and 520 deletions

View file

@ -53,4 +53,4 @@ struct BuiltinTypeFunctions
const BuiltinTypeFunctions& builtinTypeFunctions(); const BuiltinTypeFunctions& builtinTypeFunctions();
} } // namespace Luau

View file

@ -341,7 +341,6 @@ struct Constraint
DenseHashSet<TypeId> getMaybeMutatedFreeTypes_DEPRECATED() const; DenseHashSet<TypeId> getMaybeMutatedFreeTypes_DEPRECATED() const;
TypeIds getMaybeMutatedFreeTypes() const; TypeIds getMaybeMutatedFreeTypes() const;
}; };
using ConstraintPtr = std::unique_ptr<Constraint>; using ConstraintPtr = std::unique_ptr<Constraint>;

View file

@ -92,6 +92,11 @@ struct GenericTypeFinder : TypeOnceVisitor
{ {
bool found = false; bool found = false;
GenericTypeFinder()
: TypeOnceVisitor("GenericTypeFinder")
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
return !found; return !found;

View file

@ -65,4 +65,4 @@ private:
DenseHashSet<T> elementSet{nullptr}; DenseHashSet<T> elementSet{nullptr};
}; };
} } // namespace Luau

View file

@ -12,8 +12,8 @@ namespace Luau
struct RecursionLimitException : public InternalCompilerError struct RecursionLimitException : public InternalCompilerError
{ {
RecursionLimitException() RecursionLimitException(const std::string system)
: InternalCompilerError("Internal recursion counter limit exceeded") : InternalCompilerError("Internal recursion counter limit exceeded in " + system)
{ {
} }
}; };
@ -38,12 +38,12 @@ protected:
struct RecursionLimiter : RecursionCounter struct RecursionLimiter : RecursionCounter
{ {
RecursionLimiter(int* count, int limit) RecursionLimiter(const std::string system, int* count, int limit)
: RecursionCounter(count) : RecursionCounter(count)
{ {
if (limit > 0 && *count > limit) if (limit > 0 && *count > limit)
{ {
throw RecursionLimitException(); throw RecursionLimitException(system);
} }
} }
}; };

View file

@ -157,8 +157,10 @@ struct Subtyping
Variance variance = Variance::Covariant; Variance variance = Variance::Covariant;
using SeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>; using SeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
using SeenTypePackSet = Set<std::pair<TypePackId, TypePackId>, TypePairHash>;
SeenSet seenTypes{{}}; SeenSet seenTypes{{}};
SeenTypePackSet seenPacks{{}};
Subtyping( Subtyping(
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,

View file

@ -2,6 +2,7 @@
#pragma once #pragma once
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include <memory> #include <memory>
@ -134,6 +135,10 @@ std::string dump(TypeId ty);
std::string dump(const std::optional<TypeId>& ty); std::string dump(const std::optional<TypeId>& ty);
std::string dump(TypePackId ty); std::string dump(TypePackId ty);
std::string dump(const std::optional<TypePackId>& ty); std::string dump(const std::optional<TypePackId>& ty);
std::string dump(const std::vector<TypeId>& types);
std::string dump(DenseHashMap<TypeId, TypeId>& types);
std::string dump(DenseHashMap<TypePackId, TypePackId>& types);
std::string dump(const Constraint& c); std::string dump(const Constraint& c);
std::string dump(const std::shared_ptr<Scope>& scope, const char* name); std::string dump(const std::shared_ptr<Scope>& scope, const char* name);
@ -153,4 +158,5 @@ inline std::string toString(const TypeOrPack& tyOrTp)
std::string dump(const TypeOrPack& tyOrTp); std::string dump(const TypeOrPack& tyOrTp);
std::string toStringVector(const std::vector<TypeId>& types, ToStringOptions& opts);
} // namespace Luau } // namespace Luau

View file

@ -32,8 +32,8 @@ public:
TypeIds(const TypeIds&) = default; TypeIds(const TypeIds&) = default;
TypeIds& operator=(const TypeIds&) = default; TypeIds& operator=(const TypeIds&) = default;
TypeIds(TypeIds&&) = default; TypeIds(TypeIds&&) noexcept = default;
TypeIds& operator=(TypeIds&&) = default; TypeIds& operator=(TypeIds&&) noexcept = default;
void insert(TypeId ty); void insert(TypeId ty);
/// Erase every element that does not also occur in tys /// Erase every element that does not also occur in tys

View file

@ -231,7 +231,7 @@ std::string toString(const TypePath::Path& path, bool prefixDot = false);
/// Converts a Path to a human readable string for error reporting. /// Converts a Path to a human readable string for error reporting.
std::string toStringHuman(const TypePath::Path& path); std::string toStringHuman(const TypePath::Path& path);
// TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping` // TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping2`
std::optional<TypeOrPack> traverse_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse_DEPRECATED(TypeId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeOrPack> traverse_DEPRECATED(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse( std::optional<TypeOrPack> traverse(

View file

@ -73,6 +73,8 @@ struct GenericTypeVisitor
{ {
using Set = S; using Set = S;
const std::string visitorName;
Set seen; Set seen;
bool skipBoundTypes = false; bool skipBoundTypes = false;
int recursionCounter = 0; int recursionCounter = 0;
@ -80,8 +82,9 @@ struct GenericTypeVisitor
GenericTypeVisitor() = default; GenericTypeVisitor() = default;
explicit GenericTypeVisitor(Set seen, bool skipBoundTypes = false) explicit GenericTypeVisitor(const std::string visitorName, Set seen, bool skipBoundTypes = false)
: seen(std::move(seen)) : visitorName(visitorName)
, seen(std::move(seen))
, skipBoundTypes(skipBoundTypes) , skipBoundTypes(skipBoundTypes)
{ {
} }
@ -215,7 +218,7 @@ struct GenericTypeVisitor
void traverse(TypeId ty) void traverse(TypeId ty)
{ {
RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit}; RecursionLimiter limiter{visitorName, &recursionCounter, FInt::LuauVisitRecursionLimit};
if (visit_detail::hasSeen(seen, ty)) if (visit_detail::hasSeen(seen, ty))
{ {
@ -527,8 +530,8 @@ struct GenericTypeVisitor
*/ */
struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>> struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
{ {
explicit TypeVisitor(bool skipBoundTypes = false) explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes = false)
: GenericTypeVisitor{{}, skipBoundTypes} : GenericTypeVisitor{visitorName, {}, skipBoundTypes}
{ {
} }
}; };
@ -536,8 +539,8 @@ struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
/// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it. /// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it.
struct TypeOnceVisitor : GenericTypeVisitor<DenseHashSet<void*>> struct TypeOnceVisitor : GenericTypeVisitor<DenseHashSet<void*>>
{ {
explicit TypeOnceVisitor(bool skipBoundTypes = false) explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes = false)
: GenericTypeVisitor{DenseHashSet<void*>{nullptr}, skipBoundTypes} : GenericTypeVisitor{visitorName, DenseHashSet<void*>{nullptr}, skipBoundTypes}
{ {
} }
}; };

View file

@ -583,7 +583,9 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
} }
else else
return checkOverloadedDocumentationSymbol(module, propIt->second.type_DEPRECATED(), parentExpr, propIt->second.documentationSymbol); return checkOverloadedDocumentationSymbol(
module, propIt->second.type_DEPRECATED(), parentExpr, propIt->second.documentationSymbol
);
} }
} }
else if (const ExternType* etv = get<ExternType>(parentTy)) else if (const ExternType* etv = get<ExternType>(parentTy))

View file

@ -771,7 +771,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (iter == end(context.arguments)) if (iter == end(context.arguments))
{ {
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); context.typechecker->reportError(
CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location
);
return true; return true;
} }
@ -823,7 +825,8 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location; Location location =
context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location;
// use subtyping instead here // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -845,7 +848,6 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
} }
return true; return true;
} }
else else
{ {
@ -863,7 +865,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
if (!fmt) if (!fmt)
{ {
context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); context.typechecker->reportError(
CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location
);
return true; return true;
} }
@ -887,7 +891,8 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; TypeId expectedTy = expected[i];
Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location; Location location =
context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location;
// use subtyping instead here // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -895,15 +900,15 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy))
{ {
case ErrorSuppression::Suppress: case ErrorSuppression::Suppress:
break; break;
case ErrorSuppression::NormalizationFailed: case ErrorSuppression::NormalizationFailed:
break; break;
case ErrorSuppression::DoNotSuppress: case ErrorSuppression::DoNotSuppress:
Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result);
if (!reasonings.suppressed) if (!reasonings.suppressed)
context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location);
} }
} }
} }

View file

@ -124,7 +124,7 @@ std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunctionApp(
return std::nullopt; return std::nullopt;
} }
} } // namespace
TypeFunctionReductionResult<TypeId> notTypeFunction( TypeFunctionReductionResult<TypeId> notTypeFunction(
TypeId instance, TypeId instance,
@ -1034,6 +1034,12 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
struct FindRefinementBlockers : TypeOnceVisitor struct FindRefinementBlockers : TypeOnceVisitor
{ {
DenseHashSet<TypeId> found{nullptr}; DenseHashSet<TypeId> found{nullptr};
FindRefinementBlockers()
: TypeOnceVisitor("FindRefinementBlockers")
{
}
bool visit(TypeId ty, const BlockedType&) override bool visit(TypeId ty, const BlockedType&) override
{ {
found.insert(ty); found.insert(ty);
@ -1056,7 +1062,7 @@ struct ContainsRefinableType : TypeOnceVisitor
{ {
bool found = false; bool found = false;
ContainsRefinableType() ContainsRefinableType()
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor("ContainsRefinableType", /* skipBoundTypes */ true)
{ {
} }
@ -1163,7 +1169,7 @@ struct RefineTypeScrubber : public Substitution
return !is<UnionType, IntersectionType>(ty); return !is<UnionType, IntersectionType>(ty);
} }
TypeId clean(TypeId ty) override TypeId clean(TypeId ty) override
{ {
// NOTE: this feels pretty similar to other places where we try to // NOTE: this feels pretty similar to other places where we try to
// filter over a set type, may be worth combining those in the future. // filter over a set type, may be worth combining those in the future.
@ -1199,7 +1205,6 @@ struct RefineTypeScrubber : public Substitution
} }
return ty; return ty;
} }
}; };
bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen) bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen)
@ -1431,7 +1436,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {resultTy, {}}; return {resultTy, {}};
} }
}; };
// refine target with each discriminant type in sequence (reverse of insertion order) // refine target with each discriminant type in sequence (reverse of insertion order)
@ -1494,7 +1498,7 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
DenseHashSet<TypeId> blockingTypes{nullptr}; DenseHashSet<TypeId> blockingTypes{nullptr};
explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx) explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor("CollectUnionTypeOptions", /* skipBoundTypes */ true)
, ctx(ctx) , ctx(ctx)
{ {
} }
@ -1764,7 +1768,8 @@ bool computeKeysOf_DEPRECATED(TypeId ty, Set<std::string>& result, DenseHashSet<
return false; return false;
} }
namespace { namespace
{
/** /**
* Computes the keys of `ty` into `result` * Computes the keys of `ty` into `result`
@ -1847,7 +1852,7 @@ bool computeKeysOf(TypeId ty, Set<std::optional<std::string>>& result, DenseHash
return false; return false;
} }
} } // namespace
TypeFunctionReductionResult<TypeId> keyofFunctionImpl( TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
@ -2824,4 +2829,4 @@ const BuiltinTypeFunctions& builtinTypeFunctions()
return *result; return *result;
} }
} } // namespace Luau

View file

@ -24,7 +24,8 @@ struct ReferenceCountInitializer_DEPRECATED : TypeOnceVisitor
bool traverseIntoTypeFunctions = true; bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer_DEPRECATED(DenseHashSet<TypeId>* result) explicit ReferenceCountInitializer_DEPRECATED(DenseHashSet<TypeId>* result)
: result(result) : TypeOnceVisitor("ReferenceCountInitializer_DEPRECATED")
, result(result)
{ {
} }
@ -78,7 +79,8 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool traverseIntoTypeFunctions = true; bool traverseIntoTypeFunctions = true;
explicit ReferenceCountInitializer(NotNull<TypeIds> result) explicit ReferenceCountInitializer(NotNull<TypeIds> result)
: result(result) : TypeOnceVisitor("ReferenceCountInitializer")
, result(result)
{ {
} }

View file

@ -139,7 +139,10 @@ struct HasFreeType : TypeOnceVisitor
{ {
bool result = false; bool result = false;
HasFreeType() {} HasFreeType()
: TypeOnceVisitor("TypeOnceVisitor")
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
@ -640,6 +643,11 @@ struct FindSimplificationBlockers : TypeOnceVisitor
{ {
bool found = false; bool found = false;
FindSimplificationBlockers()
: TypeOnceVisitor("FindSimplificationBlockers")
{
}
bool visit(TypeId) override bool visit(TypeId) override
{ {
return !found; return !found;
@ -1026,7 +1034,7 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
{ {
RecursionLimiter limiter{&recursionCount, FInt::LuauCheckRecursionLimit}; RecursionLimiter limiter{"ConstraintGenerator", &recursionCount, FInt::LuauCheckRecursionLimit};
if (auto s = stat->as<AstStatBlock>()) if (auto s = stat->as<AstStatBlock>())
return visit(scope, s); return visit(scope, s);

View file

@ -34,7 +34,6 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch) LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck) LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint) LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint)
LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking)
namespace Luau namespace Luau
{ {
@ -285,7 +285,8 @@ struct InstantiationQueuer : TypeOnceVisitor
Location location; Location location;
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver) explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
: solver(solver) : TypeOnceVisitor("InstantiationQueuer")
, solver(solver)
, scope(scope) , scope(scope)
, location(location) , location(location)
{ {
@ -432,7 +433,8 @@ void ConstraintSolver::run()
{ {
for (TypeId ty : constraintSet.freeTypes) for (TypeId ty : constraintSet.freeTypes)
{ {
if (auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(ty); it == mutatedFreeTypeToConstraint_DEPRECATED.end() || it->second.empty()) if (auto it = mutatedFreeTypeToConstraint_DEPRECATED.find(ty);
it == mutatedFreeTypeToConstraint_DEPRECATED.end() || it->second.empty())
generalizeOneType(ty); generalizeOneType(ty);
} }
} }
@ -633,8 +635,7 @@ void ConstraintSolver::finalizeTypeFunctions()
if (get<TypeFunctionInstanceType>(ty)) if (get<TypeFunctionInstanceType>(ty))
{ {
TypeFunctionContext context{NotNull{this}, constraint->scope, NotNull{constraint}}; TypeFunctionContext context{NotNull{this}, constraint->scope, NotNull{constraint}};
FunctionGraphReductionResult result = FunctionGraphReductionResult result = reduceTypeFunctions(t, constraint->location, NotNull{&context}, true);
reduceTypeFunctions(t, constraint->location, NotNull{&context}, true);
for (TypeId r : result.reducedTypes) for (TypeId r : result.reducedTypes)
unblock(r, constraint->location); unblock(r, constraint->location);
@ -663,7 +664,8 @@ struct TypeSearcher : TypeVisitor
} }
explicit TypeSearcher(TypeId needle, Polarity initialPolarity) explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
: needle(needle) : TypeVisitor("TypeSearcher")
, needle(needle)
, current(initialPolarity) , current(initialPolarity)
{ {
} }
@ -1195,7 +1197,8 @@ struct InfiniteTypeFinder : TypeOnceVisitor
bool foundInfiniteType = false; bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope) explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
: solver(solver) : TypeOnceVisitor("InfiniteTypeFinder")
, solver(solver)
, signature(signature) , signature(signature)
, scope(scope) , scope(scope)
{ {
@ -1235,7 +1238,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
auto cTarget = follow(c.target); auto cTarget = follow(c.target);
LUAU_ASSERT(get<PendingExpansionType>(cTarget)); LUAU_ASSERT(get<PendingExpansionType>(cTarget));
// We do this check here to ensure that we don't bind an alias to itself // We do this check here to ensure that we don't bind an alias to itself
if (FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2 && occursCheck(cTarget, result)) if (occursCheck(cTarget, result))
{ {
reportError(OccursCheckFailed{}, constraint->location); reportError(OccursCheckFailed{}, constraint->location);
bind(constraint, cTarget, builtinTypes->errorType); bind(constraint, cTarget, builtinTypes->errorType);
@ -1656,6 +1659,11 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor
bool found = false; bool found = false;
ContainsGenerics_DEPRECATED()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED")
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
return !found; return !found;
@ -1736,7 +1744,8 @@ struct ContainsGenerics : public TypeOnceVisitor
NotNull<DenseHashSet<const void*>> generics; NotNull<DenseHashSet<const void*>> generics;
explicit ContainsGenerics(NotNull<DenseHashSet<const void*>> generics) explicit ContainsGenerics(NotNull<DenseHashSet<const void*>> generics)
: generics{generics} : TypeOnceVisitor("ContainsGenerics")
, generics{generics}
{ {
} }
@ -2139,7 +2148,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
Set<TypeId>& seen Set<TypeId>& seen
) )
{ {
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit}; RecursionLimiter _rl{"ConstraintSolver::tryDispatchHasIndexer", &recursionDepth, FInt::LuauSolverRecursionLimit};
subjectType = follow(subjectType); subjectType = follow(subjectType);
indexType = follow(indexType); indexType = follow(indexType);
@ -2315,6 +2324,11 @@ struct BlockedTypeFinder : TypeOnceVisitor
{ {
std::optional<TypeId> blocked; std::optional<TypeId> blocked;
BlockedTypeFinder()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED")
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
// If we've already found one, stop traversing. // If we've already found one, stop traversing.
@ -2873,7 +2887,7 @@ struct FindAllUnionMembers : TypeOnceVisitor
TypeIds blockedTys; TypeIds blockedTys;
FindAllUnionMembers() FindAllUnionMembers()
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor("FindAllUnionMembers", /* skipBoundTypes */ true)
{ {
} }
@ -2959,7 +2973,7 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor
bool found = false; bool found = false;
explicit ContainsAnyGeneric() explicit ContainsAnyGeneric()
: TypeOnceVisitor(true) : TypeOnceVisitor("ContainsAnyGeneric", /* skipBoundTypes */ true)
{ {
} }
@ -2971,7 +2985,10 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor
bool visit(TypePackId ty) override bool visit(TypePackId ty) override
{ {
found = found || is<GenericTypePack>(ty); if (FFlag::LuauContainsAnyGenericFollowBeforeChecking)
found = found || is<GenericTypePack>(follow(ty));
else
found = found || is<GenericTypePack>(ty);
return !found; return !found;
} }
@ -3367,7 +3384,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
return {{}, builtinTypes->errorType}; return {{}, builtinTypes->errorType};
} }
TypeId indexType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? follow(*indexProp->second.readTy) : follow(indexProp->second.type_DEPRECATED()); TypeId indexType =
FFlag::LuauRemoveTypeCallsForReadWriteProps ? follow(*indexProp->second.readTy) : follow(indexProp->second.type_DEPRECATED());
if (auto ft = get<FunctionType>(indexType)) if (auto ft = get<FunctionType>(indexType))
{ {
@ -3654,7 +3672,8 @@ struct Blocker : TypeOnceVisitor
bool blocked = false; bool blocked = false;
explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint) explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint)
: solver(solver) : TypeOnceVisitor("Blocker")
, solver(solver)
, constraint(constraint) , constraint(constraint)
{ {
} }

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h" #include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional) LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
@ -262,8 +261,7 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const char* const kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) static const char* const kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC(
? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties -- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
declare extern type vector with declare extern type vector with
@ -291,35 +289,6 @@ declare vector: {
one: vector, one: vector,
} }
)BUILTIN_SRC"
: R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
declare class vector
x: number
y: number
z: number
end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC"; )BUILTIN_SRC";
std::string getBuiltinDefinitionSource() std::string getBuiltinDefinitionSource()

View file

@ -870,7 +870,7 @@ struct ErrorConverter
std::string operator()(const CannotCheckDynamicStringFormatCalls& e) const std::string operator()(const CannotCheckDynamicStringFormatCalls& e) const
{ {
return "We cannot statically check the type of `string.format` when called with a format string that is not statically known.\n" return "We cannot statically check the type of `string.format` when called with a format string that is not statically known.\n"
"If you'd like to use an unchecked `string.format` call, you can cast the format string to `any` using `:: any`."; "If you'd like to use an unchecked `string.format` call, you can cast the format string to `any` using `:: any`.";
} }

View file

@ -81,7 +81,9 @@ struct IndexerIndexCollector : public TypeOnceVisitor
{ {
NotNull<TypeIds> indexes; NotNull<TypeIds> indexes;
explicit IndexerIndexCollector(NotNull<TypeIds> indexes) : TypeOnceVisitor(/* skipBoundTypes */ true), indexes(indexes) explicit IndexerIndexCollector(NotNull<TypeIds> indexes)
: TypeOnceVisitor("IndexerIndexCollector", /* skipBoundTypes */ true)
, indexes(indexes)
{ {
} }
@ -100,7 +102,6 @@ struct IndexerIndexCollector : public TypeOnceVisitor
{ {
return true; return true;
} }
}; };
struct IndexCollector : public TypeOnceVisitor struct IndexCollector : public TypeOnceVisitor
@ -108,7 +109,9 @@ struct IndexCollector : public TypeOnceVisitor
NotNull<TypeArena> arena; NotNull<TypeArena> arena;
TypeIds indexes; TypeIds indexes;
explicit IndexCollector(NotNull<TypeArena> arena) : TypeOnceVisitor(/* skipBoundTypes */ true), arena(arena) explicit IndexCollector(NotNull<TypeArena> arena)
: TypeOnceVisitor("IndexCollector", /* skipBoundTypes */ true)
, arena(arena)
{ {
} }
@ -140,10 +143,9 @@ struct IndexCollector : public TypeOnceVisitor
return false; return false;
} }
}; };
} } // namespace
bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr) bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr)
{ {
@ -156,17 +158,11 @@ bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr)
ic.traverse(*ty); ic.traverse(*ty);
if (ic.indexes.size() > 1) if (ic.indexes.size() > 1)
{ {
applyExpectedType( applyExpectedType(arena->addType(UnionType{ic.indexes.take()}), expr->index);
arena->addType(UnionType{ic.indexes.take()}),
expr->index
);
} }
else if (ic.indexes.size() == 1) else if (ic.indexes.size() == 1)
{ {
applyExpectedType( applyExpectedType(*ic.indexes.begin(), expr->index);
*ic.indexes.begin(),
expr->index
);
} }
} }
@ -314,4 +310,4 @@ void ExpectedTypeVisitor::applyExpectedType(TypeId expectedType, const AstExpr*
} }
} }
} // namespace Luau } // namespace Luau

View file

@ -619,11 +619,10 @@ struct UsageFinder : public AstVisitor
{ {
auto def = dfg->getDef(local); auto def = dfg->getDef(local);
localBindingsReferenced.emplace_back(def, local->local); localBindingsReferenced.emplace_back(def, local->local);
symbolsToRefine.emplace_back(def, Symbol(local->local)); symbolsToRefine.emplace_back(def, Symbol(local->local));
} }
else else
localBindingsReferenced.emplace_back(dfg->getDef(local), local->local); localBindingsReferenced.emplace_back(dfg->getDef(local), local->local);
} }
return true; return true;
} }
@ -635,7 +634,7 @@ struct UsageFinder : public AstVisitor
if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements)
{ {
auto def = dfg->getDef(global); auto def = dfg->getDef(global);
symbolsToRefine.emplace_back(def, Symbol(global->name)); symbolsToRefine.emplace_back(def, Symbol(global->name));
} }
return true; return true;
} }

View file

@ -1016,7 +1016,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
return; return;
} }
ModulePtr module = check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, std::move(typeCheckLimits)); ModulePtr module =
check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, std::move(typeCheckLimits));
double duration = getTimestamp() - timestamp; double duration = getTimestamp() - timestamp;
@ -1368,6 +1369,11 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor struct InternalTypeFinder : TypeOnceVisitor
{ {
InternalTypeFinder()
: TypeOnceVisitor("InternalTypeFinder")
{
}
bool visit(TypeId, const ExternType&) override bool visit(TypeId, const ExternType&) override
{ {
return false; return false;

View file

@ -46,7 +46,7 @@ struct MutatingGeneralizer : TypeOnceVisitor
DenseHashMap<const void*, size_t> positiveTypes, DenseHashMap<const void*, size_t> positiveTypes,
DenseHashMap<const void*, size_t> negativeTypes DenseHashMap<const void*, size_t> negativeTypes
) )
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor("MutatingGeneralizer", /* skipBoundTypes */ true)
, arena(arena) , arena(arena)
, builtinTypes(builtinTypes) , builtinTypes(builtinTypes)
, scope(scope) , scope(scope)
@ -338,7 +338,7 @@ struct FreeTypeSearcher : TypeVisitor
NotNull<DenseHashSet<TypeId>> cachedTypes; NotNull<DenseHashSet<TypeId>> cachedTypes;
explicit FreeTypeSearcher(NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes) explicit FreeTypeSearcher(NotNull<Scope> scope, NotNull<DenseHashSet<TypeId>> cachedTypes)
: TypeVisitor(/*skipBoundTypes*/ true) : TypeVisitor("FreeTypeSearcher", /*skipBoundTypes*/ true)
, scope(scope) , scope(scope)
, cachedTypes(cachedTypes) , cachedTypes(cachedTypes)
{ {
@ -649,7 +649,7 @@ struct TypeCacher : TypeOnceVisitor
DenseHashSet<TypePackId> uncacheablePacks{nullptr}; DenseHashSet<TypePackId> uncacheablePacks{nullptr};
explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes) explicit TypeCacher(NotNull<DenseHashSet<TypeId>> cachedTypes)
: TypeOnceVisitor(/* skipBoundTypes */ false) : TypeOnceVisitor("TypeCacher", /* skipBoundTypes */ false)
, cachedTypes(cachedTypes) , cachedTypes(cachedTypes)
{ {
} }
@ -1136,7 +1136,6 @@ struct TypeRemover
} }
} }
} }
}; };
void removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle) void removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
@ -1145,7 +1144,7 @@ void removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, Ty
tr.process(haystack); tr.process(haystack);
} }
} } // namespace
GeneralizationResult<TypeId> generalizeType( GeneralizationResult<TypeId> generalizeType(
NotNull<TypeArena> arena, NotNull<TypeArena> arena,
@ -1410,7 +1409,8 @@ struct GenericCounter : TypeVisitor
Polarity polarity = Polarity::Positive; Polarity polarity = Polarity::Positive;
explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes) explicit GenericCounter(NotNull<DenseHashSet<TypeId>> cachedTypes)
: cachedTypes(cachedTypes) : TypeVisitor("GenericCounter")
, cachedTypes(cachedTypes)
{ {
} }

View file

@ -23,7 +23,8 @@ struct InferPolarity : TypeVisitor
Polarity polarity = Polarity::Positive; Polarity polarity = Polarity::Positive;
explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope) explicit InferPolarity(NotNull<TypeArena> arena, NotNull<Scope> scope)
: arena(arena) : TypeVisitor("InferPolarity")
, arena(arena)
, scope(scope) , scope(scope)
{ {
} }

View file

@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauNormalizationLimitTyvarUnionSize)
namespace Luau namespace Luau
{ {
@ -1530,6 +1531,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
return NormalizationResult::True; return NormalizationResult::True;
} }
if (FFlag::LuauNormalizationLimitTyvarUnionSize)
{
if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit))
return NormalizationResult::HitLimits;
}
for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++) for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++)
{ {
TypeId tyvar = it->first; TypeId tyvar = it->first;

View file

@ -11,7 +11,7 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
namespace Luau namespace Luau
{ {
@ -247,9 +247,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
) )
{ {
TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits}; TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits};
FunctionGraphReductionResult result = reduceTypeFunctions( FunctionGraphReductionResult result = reduceTypeFunctions(fnTy, callLoc, NotNull{&context}, /*force=*/true);
fnTy, callLoc, NotNull{&context}, /*force=*/true
);
if (!result.errors.empty()) if (!result.errors.empty())
return {OverloadIsNonviable, result.errors}; return {OverloadIsNonviable, result.errors};
@ -330,7 +328,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
return {Analysis::Ok, {}}; return {Analysis::Ok, {}};
} }
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
// If we have an arity mismatch with generic type pack parameters, then subPath matches Args :: Tail :: ... // If we have an arity mismatch with generic type pack parameters, then subPath matches Args :: Tail :: ...
// and superPath matches Args :: ... // and superPath matches Args :: ...
@ -405,15 +403,15 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
: argExprs->size() != 0 ? argExprs->back()->location : argExprs->size() != 0 ? argExprs->back()->location
: fnExpr->location; : fnExpr->location;
std::optional<TypeId> failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping std::optional<TypeId> failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) ? traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypeId> failedSuperTy = std::optional<TypeId> failedSuperTy =
FFlag::LuauReturnMappedGenericPacksFromSubtyping FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); : traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy);
else if (failedSubTy && failedSuperTy) else if (failedSubTy && failedSuperTy)
{ {
@ -443,7 +441,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
} }
} }
} }
else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && reason.superPath.components.size() > 1) else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && reason.superPath.components.size() > 1)
{ {
// traverseForIndex only has a value if path is of form [...PackSlice, Index] // traverseForIndex only has a value if path is of form [...PackSlice, Index]
if (const auto index = if (const auto index =
@ -466,11 +464,11 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
} }
} }
std::optional<TypePackId> failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping std::optional<TypePackId> failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForPack(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) ? traverseForPack(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); : traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypePackId> failedSuperPack = std::optional<TypePackId> failedSuperPack =
FFlag::LuauReturnMappedGenericPacksFromSubtyping FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) ? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); : traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes);

View file

@ -21,7 +21,8 @@ struct Quantifier final : TypeOnceVisitor
bool seenMutableType = false; bool seenMutableType = false;
explicit Quantifier(TypeLevel level) explicit Quantifier(TypeLevel level)
: level(level) : TypeOnceVisitor("Quantifier")
, level(level)
{ {
} }

View file

@ -229,9 +229,9 @@ void Scope::inheritAssignments(const ScopePtr& childScope)
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
for (const auto& [k, a] : childScope->lvalueTypes) for (const auto& [k, a] : childScope->lvalueTypes)
lvalueTypes[k] = a; lvalueTypes[k] = a;
} }
} }

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint) LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate) LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate)
LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion)
namespace Luau namespace Luau
{ {
@ -1426,7 +1427,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
TypeId TypeSimplifier::intersect(TypeId left, TypeId right) TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
{ {
RecursionLimiter rl(&recursionDepth, 15); RecursionLimiter rl("TypeSimplifier::intersect", &recursionDepth, 15);
left = simplify(left); left = simplify(left);
right = simplify(right); right = simplify(right);
@ -1442,6 +1443,10 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
return right; return right;
if (get<UnknownType>(right) && !get<ErrorType>(left)) if (get<UnknownType>(right) && !get<ErrorType>(left))
return left; return left;
if (FFlag::LuauSimplifyAnyAndUnion && get<AnyType>(left) && get<UnionType>(right))
return union_(builtinTypes->errorType, right);
if (FFlag::LuauSimplifyAnyAndUnion && get<UnionType>(left) && get<AnyType>(right))
return union_(builtinTypes->errorType, left);
if (get<AnyType>(left)) if (get<AnyType>(left))
return arena->addType(UnionType{{right, builtinTypes->errorType}}); return arena->addType(UnionType{{right, builtinTypes->errorType}});
if (get<AnyType>(right)) if (get<AnyType>(right))
@ -1514,7 +1519,7 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
TypeId TypeSimplifier::union_(TypeId left, TypeId right) TypeId TypeSimplifier::union_(TypeId left, TypeId right)
{ {
RecursionLimiter rl(&recursionDepth, 15); RecursionLimiter rl("TypeSimplifier::union", &recursionDepth, 15);
left = simplify(left); left = simplify(left);
right = simplify(right); right = simplify(right);
@ -1602,7 +1607,7 @@ TypeId TypeSimplifier::simplify(TypeId ty)
TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen) TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
{ {
RecursionLimiter limiter(&recursionDepth, 60); RecursionLimiter limiter("TypeSimplifier::simplify", &recursionDepth, 60);
ty = follow(ty); ty = follow(ty);
@ -1843,7 +1848,7 @@ std::optional<TypeId> TypeSimplifier::intersectWithSimpleDiscriminant(TypeId tar
return builtinTypes->neverType; return builtinTypes->neverType;
if (property->writeTy && is<NeverType>(follow(property->writeTy))) if (property->writeTy && is<NeverType>(follow(property->writeTy)))
return builtinTypes->neverType; return builtinTypes->neverType;
// If the property we get back is pointer identical to the // If the property we get back is pointer identical to the
// original property, return the underlying property as an // original property, return the underlying property as an
// optimization. // optimization.

View file

@ -2,6 +2,7 @@
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "iostream"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
@ -21,7 +22,7 @@ LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2)
namespace Luau namespace Luau
{ {
@ -78,7 +79,7 @@ static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const Subtyp
template<typename TID> template<typename TID>
static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena) static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena)
{ {
LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping); LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2);
if (!FFlag::DebugLuauSubtypingCheckPathValidity) if (!FFlag::DebugLuauSubtypingCheckPathValidity)
return; return;
@ -513,7 +514,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
* cacheable. * cacheable.
*/ */
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
result.mappedGenericPacks = std::move(env.mappedGenericPacks); result.mappedGenericPacks = std::move(env.mappedGenericPacks);
} }
@ -530,7 +531,7 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu
SubtypingResult result = isCovariantWith(env, subTp, superTp, scope); SubtypingResult result = isCovariantWith(env, subTp, superTp, scope);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
if (!env.mappedGenericPacks.empty()) if (!env.mappedGenericPacks.empty())
result.mappedGenericPacks = std::move(env.mappedGenericPacks); result.mappedGenericPacks = std::move(env.mappedGenericPacks);
@ -543,7 +544,7 @@ SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult resu
{ {
const std::pair<TypeId, TypeId> p{subTy, superTy}; const std::pair<TypeId, TypeId> p{subTy, superTy};
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && !env.mappedGenericPacks.empty()) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !env.mappedGenericPacks.empty())
result.mappedGenericPacks = env.mappedGenericPacks; result.mappedGenericPacks = env.mappedGenericPacks;
if (result.isCacheable) if (result.isCacheable)
@ -572,6 +573,30 @@ struct SeenSetPopper
seenTypes->erase(pair); seenTypes->erase(pair);
} }
}; };
struct SeenTypePackSetPopper
{
Subtyping::SeenTypePackSet* seenTypes;
std::pair<TypePackId, TypePackId> pair;
SeenTypePackSetPopper(Subtyping::SeenTypePackSet* seenTypes, std::pair<TypePackId, TypePackId> pair)
: seenTypes(seenTypes)
, pair(std::move(pair))
{
LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2);
}
SeenTypePackSetPopper(const SeenTypePackSetPopper&) = delete;
SeenTypePackSetPopper& operator=(const SeenTypePackSetPopper&) = delete;
SeenTypePackSetPopper(SeenTypePackSetPopper&&) = delete;
SeenTypePackSetPopper& operator=(SeenTypePackSetPopper&&) = delete;
~SeenTypePackSetPopper()
{
LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2);
seenTypes->erase(pair);
}
};
} // namespace } // namespace
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope) SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull<Scope> scope)
@ -598,7 +623,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); const SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
if (cachedResult) if (cachedResult)
{ {
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
env.mappedGenericPacks.try_insert(genericTp, boundTp); env.mappedGenericPacks.try_insert(genericTp, boundTp);
@ -610,7 +635,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
cachedResult = env.tryFindSubtypingResult({subTy, superTy}); cachedResult = env.tryFindSubtypingResult({subTy, superTy});
if (cachedResult) if (cachedResult)
{ {
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
env.mappedGenericPacks.try_insert(genericTp, boundTp); env.mappedGenericPacks.try_insert(genericTp, boundTp);
@ -857,7 +882,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
else if (auto p = get2<SingletonType, TableType>(subTy, superTy)) else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
result = isCovariantWith(env, p, scope); result = isCovariantWith(env, p, scope);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTy, superTy, result, builtinTypes, arena); assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
else else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -870,6 +895,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
subTp = follow(subTp); subTp = follow(subTp);
superTp = follow(superTp); superTp = follow(superTp);
std::optional<SeenTypePackSetPopper> popper = std::nullopt;
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
std::pair<TypePackId, TypePackId> typePair = {subTp, superTp};
if (!seenPacks.insert(typePair))
{
SubtypingResult res;
res.isSubtype = true;
res.isCacheable = false;
return res;
}
popper.emplace(&seenPacks, std::move(typePair));
}
auto [subHead, subTail] = flatten(subTp); auto [subHead, subTail] = flatten(subTp);
auto [superHead, superTail] = flatten(superTp); auto [superHead, superTail] = flatten(superTp);
@ -908,15 +947,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// //
// <X>(X) -> () <: (T) -> () // <X>(X) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. TypePackId superTailPack;
std::vector<TypeId> headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
? std::vector<TypeId>(begin(superHead) + headSize, end(superHead)) {
: std::vector<TypeId>(begin(superHead), begin(superHead) + headSize); if (headSize == 0)
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); superTailPack = superTp;
else if (headSize == superHead.size())
superTailPack = superTail ? *superTail : builtinTypes->emptyTypePack;
else
{
auto superHeadIter = begin(superHead);
for (size_t i = 0; i < headSize; ++i)
++superHeadIter;
std::vector<TypeId> headSlice(std::move(superHeadIter), end(superHead));
superTailPack = arena->addTypePack(std::move(headSlice), superTail);
}
}
else
{
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice = std::vector<TypeId>(begin(superHead), begin(superHead) + headSize);
superTailPack = arena->addTypePack(std::move(headSlice), superTail);
}
if (TypePackId* other = env.getMappedPackBounds(*subTail)) if (TypePackId* other = env.getMappedPackBounds(*subTail))
{ {
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
const TypePack* tp = get<TypePack>(*other); const TypePack* tp = get<TypePack>(*other);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(tp->tail) : nullptr; vtp && vtp->hidden) if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(tp->tail) : nullptr; vtp && vtp->hidden)
@ -982,15 +1038,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// //
// <X...>(X...) -> () <: (T) -> () // <X...>(X...) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. TypePackId subTailPack;
std::vector<TypeId> headSlice = FFlag::LuauReturnMappedGenericPacksFromSubtyping if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
? std::vector<TypeId>(begin(subHead) + headSize, end(subHead)) {
: std::vector<TypeId>(begin(subHead), begin(subHead) + headSize); if (headSize == 0)
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); subTailPack = subTp;
else if (headSize == subHead.size())
subTailPack = subTail ? *subTail : builtinTypes->emptyTypePack;
else
{
auto subHeadIter = begin(subHead);
for (size_t i = 0; i < headSize; ++i)
++subHeadIter;
std::vector<TypeId> headSlice(std::move(subHeadIter), end(subHead));
subTailPack = arena->addTypePack(std::move(headSlice), subTail);
}
}
else
{
// Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice = std::vector<TypeId>(begin(subHead), begin(subHead) + headSize);
subTailPack = arena->addTypePack(std::move(headSlice), subTail);
}
if (TypePackId* other = env.getMappedPackBounds(*superTail)) if (TypePackId* other = env.getMappedPackBounds(*superTail))
{ {
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
const TypePack* tp = get<TypePack>(*other); const TypePack* tp = get<TypePack>(*other);
if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(tp->tail) : nullptr; vtp && vtp->hidden) if (const VariadicTypePack* vtp = tp ? get<VariadicTypePack>(tp->tail) : nullptr; vtp && vtp->hidden)
@ -1142,7 +1215,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
SubtypingResult result = SubtypingResult::all(results); SubtypingResult result = SubtypingResult::all(results);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTp, superTp, result, builtinTypes, arena); assertReasoningValid(subTp, superTp, result, builtinTypes, arena);
else else
assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes); assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes);
@ -1177,7 +1250,7 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&
} }
} }
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTy, superTy, result, builtinTypes, arena); assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
else else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -1198,7 +1271,7 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su
reasoning.variance = SubtypingVariance::Invariant; reasoning.variance = SubtypingVariance::Invariant;
} }
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTy, superTy, result, builtinTypes, arena); assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
else else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -1862,7 +1935,9 @@ SubtypingResult Subtyping::isCovariantWith(
if (FFlag::LuauRemoveTypeCallsForReadWriteProps) if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
else else
res.andAlso(isInvariantWith(env, subProp.type_DEPRECATED(), superProp.type_DEPRECATED(), scope).withBothComponent(TypePath::Property::read(name))); res.andAlso(
isInvariantWith(env, subProp.type_DEPRECATED(), superProp.type_DEPRECATED(), scope).withBothComponent(TypePath::Property::read(name))
);
} }
else else
{ {
@ -2131,6 +2206,13 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac
if (TypePackId* m = env.getMappedPackBounds(subTp)) if (TypePackId* m = env.getMappedPackBounds(subTp))
return *m == superTp; return *m == superTp;
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
// We shouldn't bind generic type packs to themselves
if (subTp == superTp)
return true;
}
env.mappedGenericPacks[subTp] = superTp; env.mappedGenericPacks[subTp] = superTp;
return true; return true;

View file

@ -52,7 +52,11 @@ namespace
struct FindCyclicTypes final : TypeVisitor struct FindCyclicTypes final : TypeVisitor
{ {
FindCyclicTypes() = default; FindCyclicTypes()
: TypeVisitor("FindCyclicTypes")
{
}
FindCyclicTypes(const FindCyclicTypes&) = delete; FindCyclicTypes(const FindCyclicTypes&) = delete;
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete; FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
@ -589,7 +593,7 @@ struct TypeStringifier
if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1) if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity); state.emit(ftv.polarity);
else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1) else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1)
state.emit(ftv.polarity); state.emit(ftv.polarity);
if (FInt::DebugLuauVerboseTypeNames >= 2) if (FInt::DebugLuauVerboseTypeNames >= 2)
@ -827,7 +831,9 @@ struct TypeStringifier
std::string openbrace = "@@@"; std::string openbrace = "@@@";
std::string closedbrace = "@@@?!"; std::string closedbrace = "@@@?!";
switch (state.opts.hideTableKind ? ((FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) ? TableState::Sealed : TableState::Unsealed) : ttv.state) switch (state.opts.hideTableKind
? ((FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) ? TableState::Sealed : TableState::Unsealed)
: ttv.state)
{ {
case TableState::Sealed: case TableState::Sealed:
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
@ -1899,6 +1905,39 @@ std::string dump(const std::optional<TypePackId>& ty)
return "nullopt"; return "nullopt";
} }
std::string dump(const std::vector<TypeId>& types)
{
return toStringVector(types, dumpOptions());
}
std::string dump(DenseHashMap<TypeId, TypeId>& types)
{
std::string s = "{";
ToStringOptions& opts = dumpOptions();
for (const auto& [key, value] : types)
{
if (s.length() == 1)
s += ", ";
s += toString(key, opts) + " : " + toString(value, opts);
}
s += "}";
return s;
}
std::string dump(DenseHashMap<TypePackId, TypePackId>& types)
{
std::string s = "{";
ToStringOptions& opts = dumpOptions();
for (const auto& [key, value] : types)
{
if (s.length() == 1)
s += ", ";
s += toString(key, opts) + " : " + toString(value, opts);
}
s += "}";
return s;
}
std::string dump(const ScopePtr& scope, const char* name) std::string dump(const ScopePtr& scope, const char* name)
{ {
auto binding = scope->linearSearchForBinding(name); auto binding = scope->linearSearchForBinding(name);
@ -2005,7 +2044,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return tos(c.resultType) + " ~ hasIndexer " + tos(c.subjectType) + " " + tos(c.indexType); return tos(c.resultType) + " ~ hasIndexer " + tos(c.subjectType) + " " + tos(c.indexType);
} }
else if constexpr (std::is_same_v<T, AssignPropConstraint>) else if constexpr (std::is_same_v<T, AssignPropConstraint>)
return tos(c.propType) + " ~ assignProp " + tos(c.lhsType) + " " + c.propName + " " + tos(c.rhsType); return tos(c.propType) + " ~ assignProp " + tos(c.lhsType) + " " + c.propName + " " + tos(c.rhsType);
else if constexpr (std::is_same_v<T, AssignIndexConstraint>) else if constexpr (std::is_same_v<T, AssignIndexConstraint>)
return "assignIndex " + tos(c.lhsType) + " " + tos(c.indexType) + " " + tos(c.rhsType); return "assignIndex " + tos(c.lhsType) + " " + tos(c.indexType) + " " + tos(c.rhsType);
else if constexpr (std::is_same_v<T, UnpackConstraint>) else if constexpr (std::is_same_v<T, UnpackConstraint>)

View file

@ -446,7 +446,7 @@ bool maybeSingleton(TypeId ty)
bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount) bool hasLength(TypeId ty, DenseHashSet<TypeId>& seen, int* recursionCount)
{ {
RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl("Type::hasLength", recursionCount, FInt::LuauTypeInferRecursionLimit);
ty = follow(ty); ty = follow(ty);

View file

@ -37,7 +37,7 @@ LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauInferActualIfElseExprType) LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
@ -161,6 +161,11 @@ struct TypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypeId> mentionedFunctions{nullptr}; DenseHashSet<TypeId> mentionedFunctions{nullptr};
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
TypeFunctionFinder()
: TypeOnceVisitor("TypeFunctionFinder")
{
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{ {
mentionedFunctions.insert(ty); mentionedFunctions.insert(ty);
@ -182,6 +187,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr}; DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack) explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
: TypeOnceVisitor("InternalTypeFunctionFinder")
{ {
TypeFunctionFinder f; TypeFunctionFinder f;
for (TypeId fn : declStack) for (TypeId fn : declStack)
@ -2052,7 +2058,8 @@ void TypeChecker2::visit(AstExprFunction* fn)
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType)) if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
reportError( reportError(
ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, fn->location ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType},
fn->location
); );
} }
} }
@ -2207,7 +2214,9 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey)
auto name = getIdentifierOfBaseVar(expr->left); auto name = getIdentifierOfBaseVar(expr->left);
reportError( reportError(
CannotInferBinaryOperation{ CannotInferBinaryOperation{
expr->op, std::move(name), isComparison ? CannotInferBinaryOperation::OpKind::Comparison : CannotInferBinaryOperation::OpKind::Operation expr->op,
std::move(name),
isComparison ? CannotInferBinaryOperation::OpKind::Comparison : CannotInferBinaryOperation::OpKind::Operation
}, },
expr->location expr->location
); );
@ -2920,11 +2929,11 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
continue; continue;
std::optional<TypeOrPack> optSubLeaf = std::optional<TypeOrPack> optSubLeaf =
FFlag::LuauReturnMappedGenericPacksFromSubtyping FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) ? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); : traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes);
std::optional<TypeOrPack> optSuperLeaf = std::optional<TypeOrPack> optSuperLeaf =
FFlag::LuauReturnMappedGenericPacksFromSubtyping FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) ? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); : traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes);
@ -3071,7 +3080,7 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
else if (auto binExpr = expr->as<AstExprBinary>(); binExpr && binExpr->op == AstExprBinary::Or) else if (auto binExpr = expr->as<AstExprBinary>(); binExpr && binExpr->op == AstExprBinary::Or)
{ {
// In this case: `{ ... } or { ... }` is literal _enough_ that // In this case: `{ ... } or { ... }` is literal _enough_ that
// we should do this covariant check. // we should do this covariant check.
auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}}); auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}});
bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs); bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs);
passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType); passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType);

View file

@ -60,6 +60,12 @@ struct InstanceCollector : TypeOnceVisitor
std::vector<const void*> typeFunctionInstanceStack; std::vector<const void*> typeFunctionInstanceStack;
std::vector<TypeId> cyclicInstance; std::vector<TypeId> cyclicInstance;
InstanceCollector()
: TypeOnceVisitor("InstanceCollector")
{
}
bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override
{ {
// TypeVisitor performs a depth-first traversal in the absence of // TypeVisitor performs a depth-first traversal in the absence of
@ -146,6 +152,11 @@ struct UnscopedGenericFinder : TypeOnceVisitor
std::vector<TypePackId> scopeGenTps; std::vector<TypePackId> scopeGenTps;
bool foundUnscoped = false; bool foundUnscoped = false;
UnscopedGenericFinder()
: TypeOnceVisitor("UnscopedGenericFinder")
{
}
bool visit(TypeId ty) override bool visit(TypeId ty) override
{ {
// Once we have found an unscoped generic, we will stop the traversal // Once we have found an unscoped generic, we will stop the traversal
@ -660,8 +671,7 @@ struct TypeFunctionReducer
if (tryGuessing(subject)) if (tryGuessing(subject))
return; return;
TypeFunctionReductionResult<TypePackId> result = TypeFunctionReductionResult<TypePackId> result = tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, ctx);
tfit->function->reducer(subject, tfit->typeArguments, tfit->packArguments, ctx);
handleTypeFunctionReduction(subject, std::move(result)); handleTypeFunctionReduction(subject, std::move(result));
} }
} }

View file

@ -24,6 +24,11 @@ struct InstanceCollector2 : TypeOnceVisitor
DenseHashSet<TypeId> cyclicInstance{nullptr}; DenseHashSet<TypeId> cyclicInstance{nullptr};
DenseHashSet<TypeId> instanceArguments{nullptr}; DenseHashSet<TypeId> instanceArguments{nullptr};
InstanceCollector2()
: TypeOnceVisitor("InstanceCollector2")
{
}
bool visit(TypeId ty, const TypeFunctionInstanceType& it) override bool visit(TypeId ty, const TypeFunctionInstanceType& it) override
{ {
// TypeOnceVisitor performs a depth-first traversal in the absence of // TypeOnceVisitor performs a depth-first traversal in the absence of

View file

@ -157,4 +157,4 @@ std::vector<TypeId> TypeIds::take()
return std::move(order); return std::move(order);
} }
} } // namespace Luau

View file

@ -442,7 +442,7 @@ struct InplaceDemoter : TypeOnceVisitor
TypeArena* arena; TypeArena* arena;
InplaceDemoter(TypeLevel level, TypeArena* arena) InplaceDemoter(TypeLevel level, TypeArena* arena)
: TypeOnceVisitor(/* skipBoundTypes= */ true) : TypeOnceVisitor("InplaceDemoter", /* skipBoundTypes= */ true)
, newLevel(level) , newLevel(level)
, arena(arena) , arena(arena)
{ {
@ -2150,7 +2150,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
for (TypeId t : utv) for (TypeId t : utv)
{ {
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl("TypeInfer::UnionType", &recursionCount, FInt::LuauTypeInferRecursionLimit);
// Not needed when we normalize types. // Not needed when we normalize types.
if (get<AnyType>(follow(t))) if (get<AnyType>(follow(t)))
@ -2189,7 +2189,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
for (TypeId t : itv->parts) for (TypeId t : itv->parts)
{ {
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit); RecursionLimiter _rl("TypeInfer::IntersectionType", &recursionCount, FInt::LuauTypeInferRecursionLimit);
if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false)) if (std::optional<TypeId> ty = getIndexTypeFromType(scope, t, name, location, /* addErrors= */ false))
parts.push_back(*ty); parts.push_back(*ty);
@ -4129,7 +4129,9 @@ void TypeChecker::checkArgumentList(
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack); auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
state.reportError(TypeError{ state.reportError(TypeError{
location, location,
CountMismatch{minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, std::move(namePath)} CountMismatch{
minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, std::move(namePath)
}
}); });
}; };
@ -4248,7 +4250,8 @@ void TypeChecker::checkArgumentList(
namePath = *path; namePath = *path;
state.reportError(TypeError{ state.reportError(TypeError{
funName.location, CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, std::move(namePath)} funName.location,
CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, std::move(namePath)}
}); });
return; return;
} }

View file

@ -4,7 +4,7 @@
#include "Luau/Error.h" #include "Luau/Error.h"
#include "Luau/TxnLog.h" #include "Luau/TxnLog.h"
#include <stdexcept> LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
namespace Luau namespace Luau
{ {
@ -455,10 +455,13 @@ std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp,
std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const DenseHashMap<TypePackId, TypePackId>& mappedGenericPacks) std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp, const DenseHashMap<TypePackId, TypePackId>& mappedGenericPacks)
{ {
LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2);
tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp;
std::vector<TypeId> flattened; std::vector<TypeId> flattened;
std::optional<TypePackId> tail = std::nullopt; std::optional<TypePackId> tail = std::nullopt;
DenseHashSet<TypePackId> seenGenericPacks{nullptr};
while (tp) while (tp)
{ {
@ -467,9 +470,10 @@ std::pair<std::vector<TypeId>, std::optional<TypePackId>> flatten(TypePackId tp,
for (; it != end(tp); ++it) for (; it != end(tp); ++it)
flattened.push_back(*it); flattened.push_back(*it);
if (const auto tpTail = it.tail(); tpTail && mappedGenericPacks.contains(*tpTail)) if (const auto tpTail = it.tail(); tpTail && !seenGenericPacks.contains(*tpTail) && mappedGenericPacks.contains(*tpTail))
{ {
tp = *mappedGenericPacks.find(*tpTail); tp = *mappedGenericPacks.find(*tpTail);
seenGenericPacks.insert(*tpTail);
continue; continue;
} }

View file

@ -16,7 +16,7 @@
#include <sstream> #include <sstream>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
// Maximum number of steps to follow when traversing a path. May not always // 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 // equate to the number of components in a path, depending on the traversal
@ -285,7 +285,7 @@ struct TraversalState
TypeOrPack current; TypeOrPack current;
NotNull<BuiltinTypes> builtinTypes; NotNull<BuiltinTypes> builtinTypes;
// TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping is clipped // TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping2 is clipped
const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks; const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks;
TypeArena* arena; TypeArena* arena;
int steps = 0; int steps = 0;
@ -417,7 +417,7 @@ struct TraversalState
{ {
auto currentPack = get<TypePackId>(current); auto currentPack = get<TypePackId>(current);
LUAU_ASSERT(currentPack); LUAU_ASSERT(currentPack);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{ {
if (const auto tp = get<TypePack>(*currentPack)) if (const auto tp = get<TypePack>(*currentPack))
{ {
@ -576,7 +576,7 @@ struct TraversalState
if (auto tail = it.tail()) if (auto tail = it.tail())
{ {
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && mappedGenericPacks && mappedGenericPacks->contains(*tail)) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && mappedGenericPacks && mappedGenericPacks->contains(*tail))
updateCurrent(*mappedGenericPacks->find(*tail)); updateCurrent(*mappedGenericPacks->find(*tail));
else else
updateCurrent(*tail); updateCurrent(*tail);
@ -595,9 +595,9 @@ struct TraversalState
if (checkInvariants()) if (checkInvariants())
return false; return false;
// TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping is clipped // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping2 is clipped
// arena and mappedGenericPacks should be NonNull once that happens // arena and mappedGenericPacks should be NonNull once that happens
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping) if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_ASSERT(arena && mappedGenericPacks); LUAU_ASSERT(arena && mappedGenericPacks);
else if (!arena || !mappedGenericPacks) else if (!arena || !mappedGenericPacks)
return false; return false;

View file

@ -33,7 +33,8 @@ struct PromoteTypeLevels final : TypeOnceVisitor
TypeLevel minLevel; TypeLevel minLevel;
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
: log(log) : TypeOnceVisitor("PromoteTypeLevels")
, log(log)
, typeArena(typeArena) , typeArena(typeArena)
, minLevel(minLevel) , minLevel(minLevel)
{ {
@ -145,7 +146,8 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
struct SkipCacheForType final : TypeOnceVisitor struct SkipCacheForType final : TypeOnceVisitor
{ {
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena) SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
: skipCacheForType(skipCacheForType) : TypeOnceVisitor("SkipCacheForType")
, skipCacheForType(skipCacheForType)
, typeArena(typeArena) , typeArena(typeArena)
{ {
} }
@ -404,7 +406,7 @@ static bool isBlocked(const TxnLog& log, TypePackId tp)
void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties) void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool isIntersection, const LiteralProperties* literalProperties)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnify_", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
++sharedState.counters.iterationCount; ++sharedState.counters.iterationCount;
@ -1448,7 +1450,7 @@ void Unifier::tryUnify(TypePackId subTp, TypePackId superTp, bool isFunctionCall
*/ */
void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall) void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCall)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnify_", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
++sharedState.counters.iterationCount; ++sharedState.counters.iterationCount;
@ -2030,7 +2032,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{ {
if (FFlag::LuauUnifierRecursionOnRestart) if (FFlag::LuauUnifierRecursionOnRestart)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnifyTables", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
tryUnify(subTy, superTy, false, isIntersection); tryUnify(subTy, superTy, false, isIntersection);
return; return;
} }
@ -2048,7 +2050,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{ {
if (errors.empty()) if (errors.empty())
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnifyTables", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
tryUnifyTables(subTy, superTy, isIntersection); tryUnifyTables(subTy, superTy, isIntersection);
} }
@ -2120,7 +2122,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{ {
if (FFlag::LuauUnifierRecursionOnRestart) if (FFlag::LuauUnifierRecursionOnRestart)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnifyTables", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
tryUnify(subTy, superTy, false, isIntersection); tryUnify(subTy, superTy, false, isIntersection);
return; return;
} }
@ -2140,7 +2142,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{ {
if (errors.empty()) if (errors.empty())
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::tryUnifyTables", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
tryUnifyTables(subTy, superTy, isIntersection); tryUnifyTables(subTy, superTy, isIntersection);
} }
@ -2732,7 +2734,7 @@ bool Unifier::occursCheck(TypeId needle, TypeId haystack, bool reversed)
bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack) bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{ {
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
bool occurrence = false; bool occurrence = false;
@ -2806,7 +2808,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
if (!log.getMutable<FreeTypePack>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericTypePack>(needle))) if (!log.getMutable<FreeTypePack>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericTypePack>(needle)))
ice("Expected needle pack to be free"); ice("Expected needle pack to be free");
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit); RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
while (!log.getMutable<ErrorTypePack>(haystack)) while (!log.getMutable<ErrorTypePack>(haystack))
{ {

View file

@ -722,7 +722,7 @@ TypeId Unifier2::mkIntersection(TypeId left, TypeId right)
OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack) OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId haystack)
{ {
RecursionLimiter _ra(&recursionCount, recursionLimit); RecursionLimiter _ra("Unifier2::occursCheck", &recursionCount, recursionLimit);
OccursCheckResult occurrence = OccursCheckResult::Pass; OccursCheckResult occurrence = OccursCheckResult::Pass;
@ -784,7 +784,7 @@ OccursCheckResult Unifier2::occursCheck(DenseHashSet<TypePackId>& seen, TypePack
if (!getMutable<FreeTypePack>(needle)) if (!getMutable<FreeTypePack>(needle))
ice->ice("Expected needle pack to be free"); ice->ice("Expected needle pack to be free");
RecursionLimiter _ra(&recursionCount, recursionLimit); RecursionLimiter _ra("Unifier2::occursCheck", &recursionCount, recursionLimit);
while (!getMutable<ErrorTypePack>(haystack)) while (!getMutable<ErrorTypePack>(haystack))
{ {

View file

@ -39,7 +39,7 @@ private:
T oldValue; T oldValue;
}; };
} } // namespace
struct FindUserTypeFunctionBlockers : TypeOnceVisitor struct FindUserTypeFunctionBlockers : TypeOnceVisitor
{ {
@ -48,7 +48,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
std::vector<TypeId> blockingTypes; std::vector<TypeId> blockingTypes;
explicit FindUserTypeFunctionBlockers(NotNull<TypeFunctionContext> ctx) explicit FindUserTypeFunctionBlockers(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor(/* skipBoundTypes */ true) : TypeOnceVisitor("FindUserTypeFunctionBlockers", /* skipBoundTypes */ true)
, ctx(ctx) , ctx(ctx)
{ {
} }

View file

@ -18,7 +18,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
// flag so that we don't break production games by reverting syntax changes. // flag so that we don't break production games by reverting syntax changes.
// See docs/SyntaxChanges.md for an explanation. // See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit) LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -1249,49 +1248,45 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
retTypes retTypes
); );
} }
else if (AstName(lexer.current().name) == "class" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern")) else if (AstName(lexer.current().name) == "class" || AstName(lexer.current().name) == "extern")
{ {
bool foundExtern = false; bool foundExtern = false;
if (FFlag::LuauDeclareExternType) if (AstName(lexer.current().name) == "extern")
{ {
if (AstName(lexer.current().name) == "extern") foundExtern = true;
{ nextLexeme();
foundExtern = true; if (AstName(lexer.current().name) != "type")
nextLexeme(); return reportStatError(
if (AstName(lexer.current().name) != "type") lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name
return reportStatError(
lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name
); );
} }
}
nextLexeme(); nextLexeme();
Location classStart = lexer.current().location; Location classStart = lexer.current().location;
Name className = parseName(FFlag::LuauDeclareExternType ? "type name" : "class name"); Name className = parseName("type name");
std::optional<AstName> superName = std::nullopt; std::optional<AstName> superName = std::nullopt;
if (AstName(lexer.current().name) == "extends") if (AstName(lexer.current().name) == "extends")
{ {
nextLexeme(); nextLexeme();
superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name; superName = parseName("supertype name").name;
} }
if (FFlag::LuauDeclareExternType) if (foundExtern)
{ {
if (foundExtern) if (AstName(lexer.current().name) != "with")
{ report(
if (AstName(lexer.current().name) != "with") lexer.current().location,
report( "Expected `with` keyword before listing properties of the external type, but got %s instead",
lexer.current().location, lexer.current().name
"Expected `with` keyword before listing properties of the external type, but got %s instead", );
lexer.current().name else
); nextLexeme();
else
nextLexeme();
}
} }
TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps); TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps);
AstTableIndexer* indexer = nullptr; AstTableIndexer* indexer = nullptr;
@ -1357,10 +1352,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt, begin).node; AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt, begin).node;
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
if (FFlag::LuauDeclareExternType) report(badIndexer->location, "Cannot have more than one indexer on an extern type");
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
{ {
@ -1427,10 +1419,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt, lexer.current()).node; AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt, lexer.current()).node;
// we lose all additional indexer expressions from the AST after error recovery here // we lose all additional indexer expressions from the AST after error recovery here
if (FFlag::LuauDeclareExternType) report(badIndexer->location, "Cannot have more than one indexer on an extern type");
report(badIndexer->location, "Cannot have more than one indexer on an extern type");
else
report(badIndexer->location, "Cannot have more than one class indexer");
} }
else else
{ {
@ -1466,13 +1455,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true); AstType* type = parseType(/* in declaration context */ true);
return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type); return allocator.alloc<AstStatDeclareGlobal>(Location(start, type->location), globalName->name, globalName->location, type);
} }
else if (FFlag::LuauDeclareExternType)
{
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'");
}
else else
{ {
return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'"); return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'");
} }
} }

View file

@ -12,13 +12,14 @@ inline bool isAnalysisFlagExperimental(const char* flag)
// or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect // or critical bugs that are found after the code has been submitted. This list is intended _only_ for flags that affect
// Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list. // Luau's type checking. Flags that may change runtime behavior (e.g.: parser or VM flags) are not appropriate for this list.
static const char* const kList[] = { static const char* const kList[] = {
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types "StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly "LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly "LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly
"LuauSolverV2", "LuauSolverV2",
"UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will require fixes in lua-apps code "UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will
// require fixes in lua-apps code
// makes sure we always have at least one entry // makes sure we always have at least one entry
nullptr, nullptr,
}; };

View file

@ -26,10 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileInlineNonConstInit)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip)
namespace Luau namespace Luau
{ {
@ -696,10 +694,7 @@ struct Compiler
// if the argument is a local that isn't mutated, we will simply reuse the existing register // if the argument is a local that isn't mutated, we will simply reuse the existing register
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written)) if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
{ {
if (FFlag::LuauCompileInlineNonConstInit) args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc, lv ? lv->init : nullptr});
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc, lv ? lv->init : nullptr});
else
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc});
} }
else else
{ {
@ -725,7 +720,7 @@ struct Compiler
{ {
pushLocal(arg.local, arg.reg, arg.allocpc); pushLocal(arg.local, arg.reg, arg.allocpc);
if (FFlag::LuauCompileInlineNonConstInit && arg.init) if (arg.init)
{ {
if (Variable* lv = variables.find(arg.local)) if (Variable* lv = variables.find(arg.local))
lv->init = arg.init; lv->init = arg.init;
@ -785,11 +780,8 @@ struct Compiler
if (Constant* var = locstants.find(local)) if (Constant* var = locstants.find(local))
var->type = Constant::Type_Unknown; var->type = Constant::Type_Unknown;
if (FFlag::LuauCompileInlineNonConstInit) if (Variable* lv = variables.find(local))
{ lv->init = nullptr;
if (Variable* lv = variables.find(local))
lv->init = nullptr;
}
} }
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body); foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body);
@ -3956,7 +3948,7 @@ struct Compiler
bool visit(AstStatTypeFunction* node) override bool visit(AstStatTypeFunction* node) override
{ {
return !FFlag::LuauCompileFixTypeFunctionSkip; return false;
} }
}; };

View file

@ -8,8 +8,6 @@
#include <limits.h> #include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileCostModelConstants)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -43,25 +41,6 @@ static uint64_t parallelMulSat(uint64_t a, int b)
return r | (s - (s >> 7)); return r | (s - (s >> 7));
} }
inline bool getNumber_DEPRECATED(AstExpr* node, double& result)
{
// since constant model doesn't use constant folding atm, we perform the basic extraction that's sufficient to handle positive/negative literals
if (AstExprConstantNumber* ne = node->as<AstExprConstantNumber>())
{
result = ne->value;
return true;
}
if (AstExprUnary* ue = node->as<AstExprUnary>(); ue && ue->op == AstExprUnary::Minus)
if (AstExprConstantNumber* ne = ue->expr->as<AstExprConstantNumber>())
{
result = -ne->value;
return true;
}
return false;
}
struct Cost struct Cost
{ {
static const uint64_t kLiteral = ~0ull; static const uint64_t kLiteral = ~0ull;
@ -132,7 +111,7 @@ struct CostVisitor : AstVisitor
Cost model(AstExpr* node) Cost model(AstExpr* node)
{ {
if (FFlag::LuauCompileCostModelConstants && constants.contains(node)) if (constants.contains(node))
return Cost(0, Cost::kLiteral); return Cost(0, Cost::kLiteral);
if (AstExprGroup* expr = node->as<AstExprGroup>()) if (AstExprGroup* expr = node->as<AstExprGroup>())
@ -280,17 +259,8 @@ struct CostVisitor : AstVisitor
int tripCount = -1; int tripCount = -1;
double from, to, step = 1; double from, to, step = 1;
if (FFlag::LuauCompileCostModelConstants) if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
{ tripCount = getTripCount(from, to, step);
if (getNumber(node->from, from) && getNumber(node->to, to) && (!node->step || getNumber(node->step, step)))
tripCount = getTripCount(from, to, step);
}
else
{
if (getNumber_DEPRECATED(node->from, from) && getNumber_DEPRECATED(node->to, to) &&
(!node->step || getNumber_DEPRECATED(node->step, step)))
tripCount = getTripCount(from, to, step);
}
loop(node->body, 1, tripCount < 0 ? 3 : tripCount); loop(node->body, 1, tripCount < 0 ? 3 : tripCount);
return false; return false;

View file

@ -3,8 +3,6 @@
#include "Luau/Lexer.h" #include "Luau/Lexer.h"
LUAU_FASTFLAG(LuauCompileInlineNonConstInit)
namespace Luau namespace Luau
{ {
namespace Compile namespace Compile
@ -85,11 +83,8 @@ struct ValueVisitor : AstVisitor
bool visit(AstExprFunction* node) override bool visit(AstExprFunction* node) override
{ {
if (FFlag::LuauCompileInlineNonConstInit) for (AstLocal* arg : node->args)
{ variables[arg].init = nullptr;
for (AstLocal* arg : node->args)
variables[arg].init = nullptr;
}
return true; return true;
} }

View file

@ -15,6 +15,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauSafeStackCheck, false)
/* /*
* This file contains most implementations of core Lua APIs from lua.h. * This file contains most implementations of core Lua APIs from lua.h.
* *
@ -139,7 +141,36 @@ int lua_checkstack(lua_State* L, int size)
res = 0; // stack overflow res = 0; // stack overflow
else if (size > 0) else if (size > 0)
{ {
luaD_checkstack(L, size); if (DFFlag::LuauSafeStackCheck)
{
if (stacklimitreached(L, size))
{
struct CallContext
{
int size;
static void run(lua_State* L, void* ud)
{
CallContext* ctx = (CallContext*)ud;
luaD_growstack(L, ctx->size);
}
} ctx = {size};
// there could be no memory to extend the stack
if (luaD_rawrunprotected(L, &CallContext::run, &ctx) != LUA_OK)
return 0;
}
else
{
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0));
}
}
else
{
luaD_checkstack(L, size);
}
expandstacklimit(L, L->top + size); expandstacklimit(L, L->top + size);
} }
return res; return res;

View file

@ -17,6 +17,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauErrorYield, false)
// keep max stack allocation request under 1GB // keep max stack allocation request under 1GB
#define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024)
@ -249,6 +251,23 @@ void luaD_checkCstack(lua_State* L)
luaD_throw(L, LUA_ERRERR); // error while handling stack error luaD_throw(L, LUA_ERRERR); // error while handling stack error
} }
static void performcall(lua_State* L, StkId func, int nresults)
{
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive;
L->isactive = true;
luaC_threadbarrier(L);
luau_execute(L); // call it
if (!oldactive)
L->isactive = false;
}
}
/* /*
** Call a function (C or Lua). The function to be called is at *func. ** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function. ** The arguments are on the stack, right after the function.
@ -277,18 +296,25 @@ void luaD_call(lua_State* L, StkId func, int nresults)
ptrdiff_t funcoffset = savestack(L, func); ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci); ptrdiff_t cioffset = saveci(L, L->ci);
if (luau_precall(L, func, nresults) == PCRLUA) if (DFFlag::LuauErrorYield)
{ // is a Lua function? {
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame performcall(L, func, nresults);
}
else
{
if (luau_precall(L, func, nresults) == PCRLUA)
{ // is a Lua function?
L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame
bool oldactive = L->isactive; bool oldactive = L->isactive;
L->isactive = true; L->isactive = true;
luaC_threadbarrier(L); luaC_threadbarrier(L);
luau_execute(L); // call it luau_execute(L); // call it
if (!oldactive) if (!oldactive)
L->isactive = false; L->isactive = false;
}
} }
bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK;
@ -314,6 +340,27 @@ void luaD_call(lua_State* L, StkId func, int nresults)
luaC_checkGC(L); luaC_checkGC(L);
} }
// Non-yieldable version of luaD_call, used primarily to call an error handler which cannot yield
void luaD_callny(lua_State* L, StkId func, int nresults)
{
if (++L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
LUAU_ASSERT(L->nCcalls > L->baseCcalls);
ptrdiff_t funcoffset = savestack(L, func);
performcall(L, func, nresults);
LUAU_ASSERT(L->status != LUA_YIELD && L->status != LUA_BREAK);
if (nresults != LUA_MULTRET)
L->top = restorestack(L, funcoffset) + nresults;
L->nCcalls--;
luaC_checkGC(L);
}
static void seterrorobj(lua_State* L, int errcode, StkId oldtop) static void seterrorobj(lua_State* L, int errcode, StkId oldtop)
{ {
switch (errcode) switch (errcode)
@ -595,7 +642,11 @@ static void callerrfunc(lua_State* L, void* ud)
setobj2s(L, L->top, L->top - 1); setobj2s(L, L->top, L->top - 1);
setobj2s(L, L->top - 1, errfunc); setobj2s(L, L->top - 1, errfunc);
incr_top(L); incr_top(L);
luaD_call(L, L->top - 2, 1);
if (DFFlag::LuauErrorYield)
luaD_callny(L, L->top - 2, 1);
else
luaD_call(L, L->top - 2, 1);
} }
static void restore_stack_limit(lua_State* L) static void restore_stack_limit(lua_State* L)

View file

@ -10,15 +10,16 @@
// returns target stack for 'n' extra elements to reallocate // returns target stack for 'n' extra elements to reallocate
// if possible, stack size growth factor is 2x // if possible, stack size growth factor is 2x
#define getgrownstacksize(L, n) ((n) <= L->stacksize ? 2 * L->stacksize : L->stacksize + (n)) #define getgrownstacksize(L, n) ((n) <= L->stacksize ? 2 * L->stacksize : L->stacksize + (n))
#define stacklimitreached(L, n) ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue))
#define luaD_checkstackfornewci(L, n) \ #define luaD_checkstackfornewci(L, n) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ if (stacklimitreached(L, (n))) \
luaD_reallocstack(L, getgrownstacksize(L, (n)), 1); \ luaD_reallocstack(L, getgrownstacksize(L, (n)), 1); \
else \ else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 1)); condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 1));
#define luaD_checkstack(L, n) \ #define luaD_checkstack(L, n) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \ if (stacklimitreached(L, (n))) \
luaD_growstack(L, n); \ luaD_growstack(L, n); \
else \ else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0));
@ -55,6 +56,7 @@ typedef void (*Pfunc)(lua_State* L, void* ud);
LUAI_FUNC CallInfo* luaD_growCI(lua_State* L); LUAI_FUNC CallInfo* luaD_growCI(lua_State* L);
LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults); LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults);
LUAI_FUNC void luaD_callny(lua_State* L, StkId func, int nresults);
LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef); LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef);
LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize); LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize);
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci); LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci);

View file

@ -14,6 +14,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauHeapDumpStringSizeOverhead)
static void validateobjref(global_State* g, GCObject* f, GCObject* t) static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{ {
LUAU_ASSERT(!isdead(g, t)); LUAU_ASSERT(!isdead(g, t));
@ -651,7 +653,10 @@ static void enumedges(EnumContext* ctx, GCObject* from, TValue* data, size_t siz
static void enumstring(EnumContext* ctx, TString* ts) static void enumstring(EnumContext* ctx, TString* ts)
{ {
enumnode(ctx, obj2gco(ts), ts->len, NULL); if (FFlag::LuauHeapDumpStringSizeOverhead)
enumnode(ctx, obj2gco(ts), sizestring(ts->len), NULL);
else
enumnode(ctx, obj2gco(ts), ts->len, NULL);
} }
static void enumtable(EnumContext* ctx, LuaTable* h) static void enumtable(EnumContext* ctx, LuaTable* h)

View file

@ -2,28 +2,200 @@
#include "luau.pb.h" #include "luau.pb.h"
static const std::string kNames[] = { static const std::string kNames[] = {
"_G", "_VERSION", "__add", "__call", "__concat", "__div", "__eq", "__idiv", "__index", "_G",
"__iter", "__le", "__len", "__lt", "__mod", "__mode", "__mul", "__namecall", "__newindex", "_VERSION",
"__pow", "__sub", "__type", "__unm", "abs", "acos", "arshift", "asin", "assert", "__add",
"atan", "atan2", "band", "bit32", "bnot", "boolean", "bor", "btest", "buffer", "__call",
"bxor", "byte", "ceil", "char", "charpattern", "clamp", "clear", "clock", "clone", "__concat",
"close", "codepoint", "codes", "collectgarbage", "concat", "copy", "coroutine", "cos", "cosh", "__div",
"countlz", "countrz", "create", "date", "debug", "deg", "difftime", "error", "exp", "__eq",
"extract", "fill", "find", "floor", "fmod", "foreach", "foreachi", "format", "freeze", "__idiv",
"frexp", "fromstring", "function", "gcinfo", "getfenv", "getmetatable", "getn", "gmatch", "gsub", "__index",
"huge", "info", "insert", "ipairs", "isfrozen", "isyieldable", "ldexp", "len", "loadstring", "__iter",
"log", "log10", "lower", "lrotate", "lshift", "match", "math", "max", "maxn", "__le",
"min", "modf", "move", "newproxy", "next", "nil", "noise", "number", "offset", "__len",
"os", "pack", "packsize", "pairs", "pcall", "pi", "pow", "print", "rad", "__lt",
"random", "randomseed", "rawequal", "rawget", "rawlen", "rawset", "readf32", "readf64", "readi16", "__mod",
"readi32", "readi8", "readstring", "readu16", "readu32", "readu8", "remove", "rep", "replace", "__mode",
"require", "resume", "reverse", "round", "rrotate", "rshift", "running", "select", "setfenv", "__mul",
"setmetatable", "sign", "sin", "sinh", "sort", "split", "sqrt", "status", "string", "__namecall",
"sub", "table", "tan", "tanh", "thread", "time", "tonumber", "tostring", "tostring", "__newindex",
"traceback", "type", "typeof", "unpack", "upper", "userdata", "utf8", "vector", "wrap", "__pow",
"writef32", "writef64", "writei16", "writei32", "writei8", "writestring", "writeu16", "writeu32", "writeu8", "__sub",
"xpcall", "yield", "types", "unknown", "never", "any", "singleton", "optional", "generic", "__type",
"negationof", "unionof", "intersectionof", "newtable", "newfunction", "__unm",
"abs",
"acos",
"arshift",
"asin",
"assert",
"atan",
"atan2",
"band",
"bit32",
"bnot",
"boolean",
"bor",
"btest",
"buffer",
"bxor",
"byte",
"ceil",
"char",
"charpattern",
"clamp",
"clear",
"clock",
"clone",
"close",
"codepoint",
"codes",
"collectgarbage",
"concat",
"copy",
"coroutine",
"cos",
"cosh",
"countlz",
"countrz",
"create",
"date",
"debug",
"deg",
"difftime",
"error",
"exp",
"extract",
"fill",
"find",
"floor",
"fmod",
"foreach",
"foreachi",
"format",
"freeze",
"frexp",
"fromstring",
"function",
"gcinfo",
"getfenv",
"getmetatable",
"getn",
"gmatch",
"gsub",
"huge",
"info",
"insert",
"ipairs",
"isfrozen",
"isyieldable",
"ldexp",
"len",
"loadstring",
"log",
"log10",
"lower",
"lrotate",
"lshift",
"match",
"math",
"max",
"maxn",
"min",
"modf",
"move",
"newproxy",
"next",
"nil",
"noise",
"number",
"offset",
"os",
"pack",
"packsize",
"pairs",
"pcall",
"pi",
"pow",
"print",
"rad",
"random",
"randomseed",
"rawequal",
"rawget",
"rawlen",
"rawset",
"readf32",
"readf64",
"readi16",
"readi32",
"readi8",
"readstring",
"readu16",
"readu32",
"readu8",
"remove",
"rep",
"replace",
"require",
"resume",
"reverse",
"round",
"rrotate",
"rshift",
"running",
"select",
"setfenv",
"setmetatable",
"sign",
"sin",
"sinh",
"sort",
"split",
"sqrt",
"status",
"string",
"sub",
"table",
"tan",
"tanh",
"thread",
"time",
"tonumber",
"tostring",
"tostring",
"traceback",
"type",
"typeof",
"unpack",
"upper",
"userdata",
"utf8",
"vector",
"wrap",
"writef32",
"writef64",
"writei16",
"writei32",
"writei8",
"writestring",
"writeu16",
"writeu32",
"writeu8",
"xpcall",
"yield",
"types",
"unknown",
"never",
"any",
"singleton",
"optional",
"generic",
"negationof",
"unionof",
"intersectionof",
"newtable",
"newfunction",
}; };
static const std::string kTypes[] = { static const std::string kTypes[] = {
@ -46,25 +218,8 @@ static const std::string kExternTypes[] = {
}; };
static const std::string kBuiltinTypes[] = { static const std::string kBuiltinTypes[] = {
"len", "len", "unm", "add", "sub", "mul", "div", "idiv", "pow", "mod", "concat",
"unm", "lt", "le", "eq", "keyof", "rawkeyof", "index", "rawget", "setmetatable", "getmetatable",
"add",
"sub",
"mul",
"div",
"idiv",
"pow",
"mod",
"concat",
"lt",
"le",
"eq",
"keyof",
"rawkeyof",
"index",
"rawget",
"setmetatable",
"getmetatable",
}; };
struct ProtoToLuau struct ProtoToLuau

View file

@ -12,7 +12,6 @@ namespace Luau
ExternTypeFixture::ExternTypeFixture(bool prepareAutocomplete) ExternTypeFixture::ExternTypeFixture(bool prepareAutocomplete)
: BuiltinsFixture(prepareAutocomplete) : BuiltinsFixture(prepareAutocomplete)
{ {
} }
Frontend& ExternTypeFixture::getFrontend() Frontend& ExternTypeFixture::getFrontend()
@ -37,7 +36,8 @@ Frontend& ExternTypeFixture::getFrontend()
}; };
getMutable<ExternType>(connectionType)->props = { getMutable<ExternType>(connectionType)->props = {
{"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}}; {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}}
};
TypeId baseClassType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId baseClassType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}});
getMutable<ExternType>(baseClassType)->props = { getMutable<ExternType>(baseClassType)->props = {
@ -114,7 +114,8 @@ Frontend& ExternTypeFixture::getFrontend()
{arena.addType(IntersectionType{{ {arena.addType(IntersectionType{{
makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}),
makeFunction(arena, vector2InstanceType, {getBuiltins()->numberType}, {vector2InstanceType}), makeFunction(arena, vector2InstanceType, {getBuiltins()->numberType}, {vector2InstanceType}),
}})}}}; }})}}
};
globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType};
addGlobalBinding(globals, "Vector2", vector2Type, "@test"); addGlobalBinding(globals, "Vector2", vector2Type, "@test");

View file

@ -17,15 +17,12 @@ namespace Luau
std::string rep(const std::string& s, size_t n); std::string rep(const std::string& s, size_t n);
} }
LUAU_FASTFLAG(LuauCompileInlineNonConstInit)
LUAU_FASTINT(LuauCompileInlineDepth) LUAU_FASTINT(LuauCompileInlineDepth)
LUAU_FASTINT(LuauCompileInlineThreshold) LUAU_FASTINT(LuauCompileInlineThreshold)
LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip)
LUAU_FASTFLAG(LuauCompileCostModelConstants)
using namespace Luau; using namespace Luau;
@ -2990,8 +2987,6 @@ TEST_CASE("TypeFunction")
TEST_CASE("NoTypeFunctionsInBytecode") TEST_CASE("NoTypeFunctionsInBytecode")
{ {
ScopedFastFlag luauCompileFixTypeFunctionSkip{FFlag::LuauCompileFixTypeFunctionSkip, true};
Luau::BytecodeBuilder bcb; Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::compileOrThrow(bcb, R"( Luau::compileOrThrow(bcb, R"(
@ -3616,8 +3611,6 @@ RETURN R4 1
TEST_CASE("CostModelRemarks") TEST_CASE("CostModelRemarks")
{ {
ScopedFastFlag luauCompileCostModelConstants{FFlag::LuauCompileCostModelConstants, true};
CHECK_EQ( CHECK_EQ(
compileWithRemarks(R"( compileWithRemarks(R"(
local a, b = ... local a, b = ...
@ -7624,8 +7617,6 @@ RETURN R1 1
TEST_CASE("InlineNonConstInitializers") TEST_CASE("InlineNonConstInitializers")
{ {
ScopedFastFlag luauCompileInlineNonConstInit{FFlag::LuauCompileInlineNonConstInit, true};
CHECK_EQ( CHECK_EQ(
"\n" + compileFunction( "\n" + compileFunction(
R"( R"(
@ -7655,8 +7646,6 @@ RETURN R0 0
TEST_CASE("InlineNonConstInitializers2") TEST_CASE("InlineNonConstInitializers2")
{ {
ScopedFastFlag luauCompileInlineNonConstInit{FFlag::LuauCompileInlineNonConstInit, true};
CHECK_EQ( CHECK_EQ(
"\n" + compileFunction( "\n" + compileFunction(
R"( R"(

View file

@ -34,9 +34,13 @@ void luaC_validate(lua_State* L);
// internal functions, declared in lvm.h - not exposed via lua.h // internal functions, declared in lvm.h - not exposed via lua.h
void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead)
LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_DYNAMIC_FASTFLAG(LuauErrorYield)
LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -754,6 +758,8 @@ TEST_CASE("Closure")
TEST_CASE("Calls") TEST_CASE("Calls")
{ {
ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true};
runConformance("calls.luau"); runConformance("calls.luau");
} }
@ -816,6 +822,8 @@ TEST_CASE("UTF8")
TEST_CASE("Coroutine") TEST_CASE("Coroutine")
{ {
ScopedFastFlag luauErrorYield{DFFlag::LuauErrorYield, true};
runConformance("coroutine.luau"); runConformance("coroutine.luau");
} }
@ -2094,13 +2102,33 @@ int slowlyOverflowStack(lua_State* L)
TEST_CASE("ApiStack") TEST_CASE("ApiStack")
{ {
StateRef globalState(luaL_newstate(), lua_close); ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true};
lua_State* L = globalState.get();
lua_pushcfunction(L, slowlyOverflowStack, "foo"); StateRef globalState(lua_newstate(blockableRealloc, nullptr), lua_close);
int result = lua_pcall(L, 0, 0, 0); lua_State* GL = globalState.get();
REQUIRE(result == LUA_ERRRUN);
CHECK(strcmp(luaL_checkstring(L, -1), "stack overflow (test)") == 0); {
lua_State* L = lua_newthread(GL);
lua_pushcfunction(L, slowlyOverflowStack, "foo");
int result = lua_pcall(L, 0, 0, 0);
REQUIRE(result == LUA_ERRRUN);
CHECK(strcmp(luaL_checkstring(L, -1), "stack overflow (test)") == 0);
}
{
lua_State* L = lua_newthread(GL);
REQUIRE(lua_checkstack(L, 100) == 1);
blockableReallocAllowed = false;
REQUIRE(lua_checkstack(L, 1000) == 0);
blockableReallocAllowed = true;
REQUIRE(lua_checkstack(L, 1000) == 1);
REQUIRE(lua_checkstack(L, LUAI_MAXCSTACK * 2) == 0);
}
} }
TEST_CASE("ApiAlloc") TEST_CASE("ApiAlloc")
@ -2315,6 +2343,8 @@ TEST_CASE("StringConversion")
TEST_CASE("GCDump") TEST_CASE("GCDump")
{ {
ScopedFastFlag luauHeapDumpStringSizeOverhead{FFlag::LuauHeapDumpStringSizeOverhead, true};
// internal function, declared in lgc.h - not exposed via lua.h // internal function, declared in lgc.h - not exposed via lua.h
extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat));
extern void luaC_enumheap( extern void luaC_enumheap(
@ -2362,14 +2392,15 @@ local function f()
x[1] = math.abs(42) x[1] = math.abs(42)
end end
function foo() function foo()
coroutine.yield() x[2] = ''
for i = 1, 10000 do x[2] ..= '1234567890' end
end end
foo() foo()
return f return f
)"); )");
lua_pushstring(CL, "=GCDump"); lua_pushstring(CL, "=GCDump");
lua_loadstring(CL); REQUIRE(lua_loadstring(CL) == 1);
lua_resume(CL, nullptr, 0); REQUIRE(lua_resume(CL, nullptr, 0) == LUA_OK);
#ifdef _WIN32 #ifdef _WIN32
const char* path = "NUL"; const char* path = "NUL";
@ -2380,6 +2411,7 @@ return f
FILE* f = fopen(path, "w"); FILE* f = fopen(path, "w");
REQUIRE(f); REQUIRE(f);
luaC_fullgc(L);
luaC_dump(L, f, nullptr); luaC_dump(L, f, nullptr);
fclose(f); fclose(f);
@ -2395,14 +2427,10 @@ return f
struct EnumContext struct EnumContext
{ {
EnumContext() Luau::DenseHashMap<void*, Node> nodes{nullptr};
: nodes{nullptr} Luau::DenseHashMap<void*, void*> edges{nullptr};
, edges{nullptr}
{
}
Luau::DenseHashMap<void*, Node> nodes; bool seenTargetString = false;
Luau::DenseHashMap<void*, void*> edges;
} ctx; } ctx;
luaC_enumheap( luaC_enumheap(
@ -2425,6 +2453,14 @@ return f
else if (tt == LUA_TTHREAD) else if (tt == LUA_TTHREAD)
CHECK(sv == "thread at unnamed:1 =GCDump"); CHECK(sv == "thread at unnamed:1 =GCDump");
} }
else if (tt == LUA_TSTRING && size >= 100000)
{
CHECK(!context.seenTargetString);
context.seenTargetString = true;
// The only string we have in this test that is 100000 characters long should include string data overhead
CHECK(size > 100000);
}
context.nodes[gco] = {gco, tt, memcat, size, name ? name : ""}; context.nodes[gco] = {gco, tt, memcat, size, name ? name : ""};
}, },
@ -2437,6 +2473,7 @@ return f
CHECK(!ctx.nodes.empty()); CHECK(!ctx.nodes.empty());
CHECK(!ctx.edges.empty()); CHECK(!ctx.edges.empty());
CHECK(ctx.seenTargetString);
} }
TEST_CASE("Interrupt") TEST_CASE("Interrupt")
@ -3296,6 +3333,7 @@ TEST_CASE("HugeFunctionLoadFailure")
REQUIRE_EQ(largeAllocationToFail, expectedTotalLargeAllocations); REQUIRE_EQ(largeAllocationToFail, expectedTotalLargeAllocations);
} }
TEST_CASE("IrInstructionLimit") TEST_CASE("IrInstructionLimit")
{ {
if (!codegen || !luau_codegen_supported()) if (!codegen || !luau_codegen_supported())

View file

@ -61,7 +61,8 @@ void ConstraintGeneratorFixture::solve(const std::string& code)
{}, {},
&logger, &logger,
NotNull{dfg.get()}, NotNull{dfg.get()},
{}}; {}
};
cs.run(); cs.run();
} }

View file

@ -5,8 +5,6 @@
#include "doctest.h" #include "doctest.h"
LUAU_FASTFLAG(LuauCompileCostModelConstants)
using namespace Luau; using namespace Luau;
namespace Luau namespace Luau
@ -133,37 +131,6 @@ end
CHECK_EQ(5, Luau::Compile::computeCost(model, args2, 1)); CHECK_EQ(5, Luau::Compile::computeCost(model, args2, 1));
} }
TEST_CASE("ControlFlow")
{
ScopedFastFlag luauCompileCostModelConstants{FFlag::LuauCompileCostModelConstants, false};
uint64_t model = modelFunction(R"(
function test(a)
while a < 0 do
a += 1
end
for i=10,1,-1 do
a += 1
end
for i in pairs({}) do
a += 1
if a % 2 == 0 then continue end
end
repeat
a += 1
if a % 2 == 0 then break end
until a > 10
return a
end
)");
const bool args1[] = {false};
const bool args2[] = {true};
CHECK_EQ(76, Luau::Compile::computeCost(model, args1, 1));
CHECK_EQ(73, Luau::Compile::computeCost(model, args2, 1));
}
TEST_CASE("Conditional") TEST_CASE("Conditional")
{ {
uint64_t model = modelFunction(R"( uint64_t model = modelFunction(R"(

View file

@ -135,7 +135,9 @@ TEST_CASE_FIXTURE(ESFixture, "string | never")
TEST_CASE_FIXTURE(ESFixture, "string | never | number") TEST_CASE_FIXTURE(ESFixture, "string | never | number")
{ {
CHECK("number | string" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->neverType, getBuiltins()->numberType}}))); CHECK(
"number | string" == simplifyStr(arena->addType(UnionType{{getBuiltins()->stringType, getBuiltins()->neverType, getBuiltins()->numberType}}))
);
} }
TEST_CASE_FIXTURE(ESFixture, "string & string") TEST_CASE_FIXTURE(ESFixture, "string & string")
@ -161,9 +163,9 @@ TEST_CASE_FIXTURE(ESFixture, "never & string")
TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)") TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)")
{ {
CHECK( CHECK(
"string" == simplifyStr(arena->addType( "string" == simplifyStr(arena->addType(IntersectionType{
IntersectionType{{getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}} {getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}
)) }))
); );
} }
@ -386,7 +388,8 @@ TEST_CASE_FIXTURE(ESFixture, "union<number, number>")
{ {
CHECK( CHECK(
"number" == "number" ==
simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}})) simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}})
)
); );
} }
@ -418,9 +421,9 @@ TEST_CASE_FIXTURE(ESFixture, "blocked & ~number & function")
TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)") TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)")
{ {
const TypeId t1 = arena->addType( const TypeId t1 = arena->addType(UnionType{
UnionType{{getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType}} {getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType}
); });
CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}}))); CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}})));
} }
@ -689,7 +692,8 @@ TEST_CASE_FIXTURE(ESFixture, "lt<number, _> == boolean")
TEST_CASE_FIXTURE(ESFixture, "unknown & ~string") TEST_CASE_FIXTURE(ESFixture, "unknown & ~string")
{ {
CHECK_EQ( CHECK_EQ(
"~string", simplifyStr(arena->addType(IntersectionType{{getBuiltins()->unknownType, arena->addType(NegationType{getBuiltins()->stringType})}})) "~string",
simplifyStr(arena->addType(IntersectionType{{getBuiltins()->unknownType, arena->addType(NegationType{getBuiltins()->stringType})}}))
); );
} }

View file

@ -696,9 +696,10 @@ Frontend& Fixture::getFrontend()
&fileResolver, &fileResolver,
&configResolver, &configResolver,
FrontendOptions{ FrontendOptions{
/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed} /* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed
}
); );
builtinTypes = f.builtinTypes; builtinTypes = f.builtinTypes;
// Fixture::Fixture begins here // Fixture::Fixture begins here
configResolver.defaultConfig.mode = Mode::Strict; configResolver.defaultConfig.mode = Mode::Strict;
@ -732,7 +733,6 @@ Frontend& Fixture::getFrontend()
BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete) BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete)
: Fixture(prepareAutocomplete) : Fixture(prepareAutocomplete)
{ {
} }
Frontend& BuiltinsFixture::getFrontend() Frontend& BuiltinsFixture::getFrontend()

View file

@ -165,7 +165,7 @@ struct Fixture
NullModuleResolver moduleResolver; NullModuleResolver moduleResolver;
std::unique_ptr<SourceModule> sourceModule; std::unique_ptr<SourceModule> sourceModule;
InternalErrorReporter ice; InternalErrorReporter ice;
std::string decorateWithTypes(const std::string& code); std::string decorateWithTypes(const std::string& code);
@ -185,8 +185,10 @@ struct Fixture
// TODO: test theory about dynamic dispatch // TODO: test theory about dynamic dispatch
NotNull<BuiltinTypes> getBuiltins(); NotNull<BuiltinTypes> getBuiltins();
virtual Frontend& getFrontend(); virtual Frontend& getFrontend();
private: private:
bool hasDumpedErrors = false; bool hasDumpedErrors = false;
protected: protected:
bool forAutocomplete = false; bool forAutocomplete = false;
std::optional<Frontend> frontend; std::optional<Frontend> frontend;

View file

@ -162,7 +162,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
Position cursorPos, Position cursorPos,
std::function<void(FragmentAutocompleteStatusResult& result)> assertions, std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
std::optional<Position> fragmentEndPosition = std::nullopt std::optional<Position> fragmentEndPosition = std::nullopt
) )
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New);
@ -179,7 +179,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
Position cursorPos, Position cursorPos,
std::function<void(FragmentAutocompleteStatusResult& result)> assertions, std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
std::optional<Position> fragmentEndPosition = std::nullopt std::optional<Position> fragmentEndPosition = std::nullopt
) )
{ {
ScopedFastFlag sff{FFlag::LuauSolverV2, false}; ScopedFastFlag sff{FFlag::LuauSolverV2, false};
this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old);
@ -238,7 +238,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
ParseResult parseResult = parseHelper(document); ParseResult parseResult = parseHelper(document);
FrontendOptions options; FrontendOptions options;
FragmentContext context{document, parseResult, options, fragmentEndPosition}; FragmentContext context{document, parseResult, options, fragmentEndPosition};
return Luau::tryFragmentAutocomplete(this->getFrontend(). module, cursorPos, context, nullCallback); return Luau::tryFragmentAutocomplete(this->getFrontend().module, cursorPos, context, nullCallback);
} }
SourceModule& getSource() SourceModule& getSource()
@ -3792,11 +3792,17 @@ if result.type == "ok" then
result. result.
end end
)"; )";
autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){ autocompleteFragmentInOldSolver(
REQUIRE(result.result); source,
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); dest,
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1); Position{8, 11},
}); [](auto& result)
{
REQUIRE(result.result);
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1);
}
);
} }
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_old_solver") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_old_solver")
@ -3825,11 +3831,17 @@ if result.type == "err" then
end end
)"; )";
autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){ autocompleteFragmentInOldSolver(
REQUIRE(result.result); source,
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); dest,
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1); Position{8, 11},
}); [](auto& result)
{
REQUIRE(result.result);
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1);
}
);
} }
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver")
@ -3858,11 +3870,17 @@ if result.type == "ok" then
result. result.
end end
)"; )";
autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){ autocompleteFragmentInNewSolver(
REQUIRE(result.result); source,
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); dest,
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1); Position{8, 11},
}); [](auto& result)
{
REQUIRE(result.result);
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
CHECK_EQ(result.result->acResults.entryMap.count("value"), 1);
}
);
} }
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver")
@ -3891,11 +3909,17 @@ if result.type == "err" then
end end
)"; )";
autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){ autocompleteFragmentInNewSolver(
REQUIRE(result.result); source,
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); dest,
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1); Position{8, 11},
}); [](auto& result)
{
REQUIRE(result.result);
CHECK_EQ(result.result->acResults.entryMap.count("type"), 1);
CHECK_EQ(result.result->acResults.entryMap.count("error"), 1);
}
);
} }
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "inline_prop_read_on_requires_provides_results") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "inline_prop_read_on_requires_provides_results")

View file

@ -623,7 +623,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "produce_errors_for_unchanged_file_with_error
getFrontend().check("Modules/A"); getFrontend().check("Modules/A");
fileResolver.source["Modules/A"] = "local p = 4 -- We have fixed the problem, but we didn't tell the getFrontend(). so it will not recheck this file!"; fileResolver.source["Modules/A"] =
"local p = 4 -- We have fixed the problem, but we didn't tell the getFrontend(). so it will not recheck this file!";
CheckResult secondResult = getFrontend().check("Modules/A"); CheckResult secondResult = getFrontend().check("Modules/A");
CHECK_EQ(1, secondResult.errors.size()); CHECK_EQ(1, secondResult.errors.size());

View file

@ -201,7 +201,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_types_point_into_globalTypes_arena")
TableType* exportsTable = getMutable<TableType>(*exports); TableType* exportsTable = getMutable<TableType>(*exports);
REQUIRE(exportsTable != nullptr); REQUIRE(exportsTable != nullptr);
TypeId signType = FFlag::LuauRemoveTypeCallsForReadWriteProps ? *exportsTable->props["sign"].readTy : exportsTable->props["sign"].type_DEPRECATED(); TypeId signType =
FFlag::LuauRemoveTypeCallsForReadWriteProps ? *exportsTable->props["sign"].readTy : exportsTable->props["sign"].type_DEPRECATED();
REQUIRE(signType != nullptr); REQUIRE(signType != nullptr);
CHECK(!isInArena(signType, module->interfaceTypes)); CHECK(!isInArena(signType, module->interfaceTypes));

View file

@ -206,7 +206,9 @@ TEST_CASE_FIXTURE(Fixture, "inline_table_props_are_also_any")
CHECK_EQ(*getBuiltins()->anyType, *ttv->props["one"].type_DEPRECATED()); CHECK_EQ(*getBuiltins()->anyType, *ttv->props["one"].type_DEPRECATED());
CHECK_EQ(*getBuiltins()->anyType, *ttv->props["two"].type_DEPRECATED()); CHECK_EQ(*getBuiltins()->anyType, *ttv->props["two"].type_DEPRECATED());
CHECK_MESSAGE(get<FunctionType>(follow(ttv->props["three"].type_DEPRECATED())), "Should be a function: " << *ttv->props["three"].type_DEPRECATED()); CHECK_MESSAGE(
get<FunctionType>(follow(ttv->props["three"].type_DEPRECATED())), "Should be a function: " << *ttv->props["three"].type_DEPRECATED()
);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_iterator_variables_are_any") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_iterator_variables_are_any")

View file

@ -16,7 +16,7 @@ LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSimplifyOutOfLine2)
LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
using namespace Luau; using namespace Luau;
namespace namespace
@ -1223,7 +1223,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauSimplifyOutOfLine2, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true},
}; };
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
@ -1999,8 +1998,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_class_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
{ {
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
AstStatBlock* stat = parseEx(R"( AstStatBlock* stat = parseEx(R"(
declare extern type Foo with declare extern type Foo with
prop: number prop: number
@ -2051,8 +2048,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with") TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
{ {
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
ParseResult result = tryParse(R"( ParseResult result = tryParse(R"(
declare extern type Foo declare extern type Foo
prop: number prop: number
@ -2108,8 +2103,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
{ {
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
AstStatBlock* stat = parseEx(R"( AstStatBlock* stat = parseEx(R"(
declare extern type Foo with declare extern type Foo with
prop: number prop: number
@ -2160,8 +2153,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with") TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
{ {
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
ParseResult result = tryParse(R"( ParseResult result = tryParse(R"(
declare extern type Foo declare extern type Foo
prop: number prop: number
@ -2281,7 +2272,7 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer")
[number]: number [number]: number
end end
)", )",
(FFlag::LuauDeclareExternType) ? "Cannot have more than one indexer on an extern type" : "Cannot have more than one class indexer" "Cannot have more than one indexer on an extern type"
); );
REQUIRE_EQ(1, p1.root->body.size); REQUIRE_EQ(1, p1.root->body.size);

View file

@ -19,6 +19,9 @@ using namespace Luau;
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
struct LimitFixture : BuiltinsFixture struct LimitFixture : BuiltinsFixture
{ {
@ -47,7 +50,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
{ {
DOES_NOT_PASS_NEW_SOLVER_GUARD(); DOES_NOT_PASS_NEW_SOLVER_GUARD();
constexpr const char* src = R"LUA( constexpr const char* src = R"LUAU(
--!strict --!strict
-- Big thanks to Dionysusnu by letting us use this code as part of our test suite! -- Big thanks to Dionysusnu by letting us use this code as part of our test suite!
@ -272,11 +275,62 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
return { return {
Result = Result, Result = Result,
} }
)LUA"; )LUAU";
CheckResult result = check(src); CheckResult result = check(src);
CHECK(hasError<CodeTooComplex>(result)); CHECK(hasError<CodeTooComplex>(result));
} }
TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5))
{
ScopedFastFlag sff[] = {
// These flags are required to surface the problem.
{FFlag::LuauSolverV2, true},
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauPushFunctionTypesInFunctionStatement, true},
// And this flag is the one that fixes it.
{FFlag::LuauSimplifyAnyAndUnion, true},
};
constexpr const char* src = R"LUAU(
local Signal = {}
Signal.ClassName = "Signal"
export type Signal<T...> = typeof(setmetatable(
{} :: {},
{} :: typeof({ __index = Signal })
))
function Signal.new<T...>(): Signal<T...>
return nil :: any
end
function Signal.Connect<T...>(self: Signal<T...>)
end
function Signal.DisconnectAll<T...>(self: Signal<T...>): ()
self._handlerListHead = false
end
function Signal.Fire<T...>(self: Signal<T...>): ()
local connection
rawget(connection, "_signal")
end
function Signal.Wait<T...>(self: Signal<T...>)
connection = self:Connect(function()
connection:Disconnect()
end)
end
function Signal.Once<T...>(self: Signal<T...>, fn: SignalHandler<T...>): Connection<T...>
connection = self:Connect(function() end)
end
)LUAU";
CheckResult result = check(src);
(void)result;
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -9,6 +9,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit) LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit)
namespace namespace
@ -62,6 +63,7 @@ struct SimplifyFixture : Fixture
TypeId anotherChildClassTy = nullptr; TypeId anotherChildClassTy = nullptr;
TypeId unrelatedClassTy = nullptr; TypeId unrelatedClassTy = nullptr;
// This only affects type stringification.
ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, true};
SimplifyFixture() SimplifyFixture()
@ -619,4 +621,26 @@ TEST_CASE_FIXTURE(SimplifyFixture, "cyclic_never_union_and_string")
CHECK(getBuiltins()->stringType == union_(leftType, getBuiltins()->stringType)); CHECK(getBuiltins()->stringType == union_(leftType, getBuiltins()->stringType));
} }
TEST_CASE_FIXTURE(SimplifyFixture, "any & (error | string)")
{
ScopedFastFlag sff{FFlag::LuauSimplifyAnyAndUnion, true};
TypeId errStringTy = arena->addType(UnionType{{getBuiltins()->errorType, getBuiltins()->stringType}});
auto res = intersect(builtinTypes->anyType, errStringTy);
CHECK("*error-type* | string" == toString(res));
}
TEST_CASE_FIXTURE(SimplifyFixture, "(error | string) & any")
{
ScopedFastFlag sff{FFlag::LuauSimplifyAnyAndUnion, true};
TypeId errStringTy = arena->addType(UnionType{{getBuiltins()->errorType, getBuiltins()->stringType}});
auto res = intersect(errStringTy, builtinTypes->anyType);
CHECK("*error-type* | string" == toString(res));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -17,7 +17,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
using namespace Luau; using namespace Luau;
@ -1240,11 +1240,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
TEST_CASE_FIXTURE(SubtypeFixture, "bill") TEST_CASE_FIXTURE(SubtypeFixture, "bill")
{ {
TypeId a = arena.addType(TableType{ TypeId a = arena.addType(TableType{
{{"a", getBuiltins()->stringType}}, TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, TypeLevel{}, nullptr, TableState::Sealed {{"a", getBuiltins()->stringType}},
TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType},
TypeLevel{},
nullptr,
TableState::Sealed
}); });
TypeId b = arena.addType(TableType{ TypeId b = arena.addType(TableType{
{{"a", getBuiltins()->stringType}}, TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, TypeLevel{}, nullptr, TableState::Sealed {{"a", getBuiltins()->stringType}},
TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType},
TypeLevel{},
nullptr,
TableState::Sealed
}); });
CHECK(isSubtype(a, b).isSubtype); CHECK(isSubtype(a, b).isSubtype);
@ -1387,7 +1395,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "<T>({ x: T }) -> T <: ({ method: <T>({ x: T }
TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance")
{ {
ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
TypeId longTy = arena.addType(UnionType{ TypeId longTy = arena.addType(UnionType{
{getBuiltins()->booleanType, {getBuiltins()->booleanType,
@ -1631,9 +1639,9 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
TypeId bTy = arena.addType(GenericType{"B"}); TypeId bTy = arena.addType(GenericType{"B"});
getMutable<GenericType>(bTy)->scope = moduleScope.get(); getMutable<GenericType>(bTy)->scope = moduleScope.get();
TypeId genericFunctionTy = TypeId genericFunctionTy = arena.addType(
arena.addType(FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, getBuiltins()->truthyType), bTy)})} FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, getBuiltins()->truthyType), bTy)})}
); );
const TypeId truthyTy = getBuiltins()->truthyType; const TypeId truthyTy = getBuiltins()->truthyType;

View file

@ -61,7 +61,8 @@ TEST_CASE_FIXTURE(Fixture, "free_types_stringify_the_same_regardless_of_solver")
{ {
ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true};
TypeArena a; TypeArena a;
TypeId t = a.addType(FreeType{getFrontend().globals.globalScope.get(), getFrontend().builtinTypes->neverType, getFrontend().builtinTypes->unknownType}); TypeId t =
a.addType(FreeType{getFrontend().globals.globalScope.get(), getFrontend().builtinTypes->neverType, getFrontend().builtinTypes->unknownType});
CHECK_EQ("'a", toString(t)); CHECK_EQ("'a", toString(t));
} }

View file

@ -744,10 +744,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauStuckTypeFunctionsStillDispatch, true}};
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
local EnumVariants = { local EnumVariants = {
@ -1787,7 +1784,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_prop
LUAU_REQUIRE_NO_ERRORS(results); LUAU_REQUIRE_NO_ERRORS(results);
CHECK_EQ(R"("" | "one")", toString(requireTypeAlias("FoobarKeys"))); CHECK_EQ(R"("" | "one")", toString(requireTypeAlias("FoobarKeys")));
CHECK_EQ(R"("" | "two")", toString(requireTypeAlias("TableKeys"))); CHECK_EQ(R"("" | "two")", toString(requireTypeAlias("TableKeys")));
} }
struct TFFixture struct TFFixture
@ -1796,7 +1792,10 @@ struct TFFixture
NotNull<TypeArena> arena{&arena_}; NotNull<TypeArena> arena{&arena_};
BuiltinTypes builtinTypes_; BuiltinTypes builtinTypes_;
NotNull<BuiltinTypes> getBuiltins(){ return NotNull{&builtinTypes_};} NotNull<BuiltinTypes> getBuiltins()
{
return NotNull{&builtinTypes_};
}
ScopePtr globalScope = std::make_shared<Scope>(getBuiltins()->anyTypePack); ScopePtr globalScope = std::make_shared<Scope>(getBuiltins()->anyTypePack);

View file

@ -2417,10 +2417,7 @@ local function ok(idx: get<>): number return idx end
)"); )");
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(4, result);
CHECK( CHECK(toString(result.errors[1]) == R"(Type function instance get<> is uninhabited)");
toString(result.errors[1]) ==
R"(Type function instance get<> is uninhabited)"
);
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block") TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block")

View file

@ -10,7 +10,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
TEST_SUITE_BEGIN("TypeAliases"); TEST_SUITE_BEGIN("TypeAliases");
@ -1244,8 +1243,6 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod
TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases") TEST_CASE_FIXTURE(Fixture, "fuzzer_cursed_type_aliases")
{ {
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
// This used to crash under the new solver: we would like this to continue // This used to crash under the new solver: we would like this to continue
// to not crash. // to not crash.
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
@ -1282,8 +1279,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_dont_crash_on_duplicate_with_typeof")
TEST_CASE_FIXTURE(Fixture, "fuzzer_more_cursed_aliases") TEST_CASE_FIXTURE(Fixture, "fuzzer_more_cursed_aliases")
{ {
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
LUAU_REQUIRE_ERRORS(check(R"( LUAU_REQUIRE_ERRORS(check(R"(
export type t138 = t0<t138> export type t138 = t0<t138>
export type t0<t0,t10,t10,t109> = t0 export type t0<t0,t10,t10,t109> = t0

View file

@ -272,7 +272,8 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_of_value_a_via_typeof_with_assignment")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK( CHECK(
result.errors[0] == (TypeError{Location{Position{2, 29}, Position{2, 30}}, TypeMismatch{getBuiltins()->nilType, getBuiltins()->numberType}}) result.errors[0] ==
(TypeError{Location{Position{2, 29}, Position{2, 30}}, TypeMismatch{getBuiltins()->nilType, getBuiltins()->numberType}})
); );
} }
else else
@ -447,7 +448,8 @@ TEST_CASE_FIXTURE(Fixture, "self_referential_type_alias")
if (FFlag::LuauRemoveTypeCallsForReadWriteProps) if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
REQUIRE(incr->readTy); REQUIRE(incr->readTy);
const FunctionType* incrFunc = FFlag::LuauRemoveTypeCallsForReadWriteProps ? get<FunctionType>(*incr->readTy) : get<FunctionType>(incr->type_DEPRECATED()); const FunctionType* incrFunc =
FFlag::LuauRemoveTypeCallsForReadWriteProps ? get<FunctionType>(*incr->readTy) : get<FunctionType>(incr->type_DEPRECATED());
REQUIRE(incrFunc); REQUIRE(incrFunc);
std::optional<TypeId> firstArg = first(incrFunc->argTypes); std::optional<TypeId> firstArg = first(incrFunc->argTypes);

View file

@ -9,7 +9,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps) LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking)
using namespace Luau; using namespace Luau;
@ -1004,7 +1005,7 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test(a: number) function test(a: number)
@ -1032,7 +1033,7 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number, b: string) function test2(a: number, b: string)
@ -1076,7 +1077,7 @@ wrapper(test2, 1, "")
TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number) function test2(a: number)
@ -1106,7 +1107,7 @@ wrapper(test2, 1)
TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number) function test2(a: number)
@ -1124,7 +1125,7 @@ wrapper(test2, "hello")
TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs") TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function test2(a: number) function test2(a: number)
@ -1851,4 +1852,63 @@ end
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
} }
TEST_CASE_FIXTURE(Fixture, "generic_type_packs_shouldnt_be_bound_to_themselves")
{
ScopedFastFlag flags[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauSubtypingCheckFunctionGenericCounts, true},
{FFlag::LuauEagerGeneralization4, true}
};
CheckResult result = check(R"(
export type t1<T...> = {
foo: (self: t1<T...>, bar: (T...) -> ()) -> ()
}
export type t2<T...> = {
baz: (self: t2<T...>) -> t1<T...>,
}
export type t3<T...> = {
f: (self: t3<T...>, T...)-> (),
g: t1<T...>,
h: t1<(Player, T...)>
}
local t2 = {}
function t2.new<T...>(): t2<T...>
end
local function create_t3<T...>(): t3<T...>
local t2_1 = t2.new()
local t2_2 = t2.new()
local my_t3 = {
f = function(_self: t3<T...>, ...: T...) end,
g = t2_1:baz(),
h = t2_2:baz()
}
return my_t3
end
)");
// Note: we just need this test not to crash
LUAU_REQUIRE_ERROR_COUNT(5, result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "follow_bound_type_packs_in_generic_type_visitor")
{
ScopedFastFlag _{FFlag::LuauContainsAnyGenericFollowBeforeChecking, true};
// Note: we just need this test not to crash
check(R"(
function (_(_,_,nil))
(if l0 then typeof else `{_:_()}`,typeof).n0<A...,A...>(l0)
function _:_():typeof<A...>()
end
function _:_().typeof<A...>()
end
end
)");
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -12,7 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
@ -1162,7 +1162,7 @@ could not be converted into
TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}; ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"( CheckResult result = check(R"(
function f<a...>() function f<a...>()

View file

@ -13,7 +13,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
using namespace Luau; using namespace Luau;
@ -823,7 +823,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any")
TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation")
{ {
ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping, true}}; ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}};
fileResolver.source["game/A"] = R"( fileResolver.source["game/A"] = R"(
function test2(a: number, b: string) function test2(a: number, b: string)

View file

@ -559,7 +559,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag sff[] ={ ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauEagerGeneralization4, true},
}; };
@ -591,7 +591,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2")
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag sff[] ={ ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauEagerGeneralization4, true},
}; };

View file

@ -127,7 +127,8 @@ struct RefinementExternTypeFixture : BuiltinsFixture
}; };
TypeId optionalPart = arena.addType(UnionType{{part, getBuiltins()->nilType}}); TypeId optionalPart = arena.addType(UnionType{{part, getBuiltins()->nilType}});
TypeId weldConstraint = getFrontend().globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); TypeId weldConstraint =
getFrontend().globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}});
getMutable<ExternType>(weldConstraint)->props = { getMutable<ExternType>(weldConstraint)->props = {
{"Part0", Property{optionalPart}}, {"Part0", Property{optionalPart}},
{"Part1", Property{optionalPart}}, {"Part1", Property{optionalPart}},
@ -2453,7 +2454,7 @@ end)
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(1.0)) TEST_CASE_FIXTURE(Fixture, "refinements_table_intersection_limits" * doctest::timeout(1.5))
{ {
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict

View file

@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
LUAU_FASTFLAG(LuauInferActualIfElseExprType) LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize)
TEST_SUITE_BEGIN("TableTests"); TEST_SUITE_BEGIN("TableTests");
@ -6129,7 +6130,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_119126_regression")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(2, results); LUAU_REQUIRE_ERROR_COUNT(2, results);
for (const auto& err: results.errors) for (const auto& err : results.errors)
{ {
auto e = get<TypeMismatch>(err); auto e = get<TypeMismatch>(err);
REQUIRE(e); REQUIRE(e);
@ -6173,5 +6174,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assert
CHECK_EQ("number", toString(requireType("myAge"))); CHECK_EQ("number", toString(requireType("myAge")));
} }
TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_normalization" * doctest::timeout(1.0))
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauNormalizationLimitTyvarUnionSize, true},
};
const std::string source =
"local res = {\n" + rep("\"foo\",\n", 100) + "}\n"
+ "local function check(index: number)\n"
+ " if res[index] == \"foo\" then\n"
+ " print(\"found a foo!\")\n"
+ " end\n"
+ "end";
LUAU_REQUIRE_NO_ERRORS(check(source));
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -1984,7 +1984,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving")
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving")
{ {
ScopedFastFlag _ {FFlag::LuauSolverV2, true}; ScopedFastFlag _{FFlag::LuauSolverV2, true};
// This is the original fuzzer version of the above issue. // This is the original fuzzer version of the above issue.
CheckResult results = check(R"( CheckResult results = check(R"(
local function l0() local function l0()
@ -2383,7 +2383,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
local l249 = require(module0) local l249 = require(module0)
_,_ = {[`{_}`]=_,[_._G._]=(_)(),[_["" + _]._G]={_=_,_=_,[_._G[_]._]=_G,},},_,(_)() _,_ = {[`{_}`]=_,[_._G._]=(_)(),[_["" + _]._G]={_=_,_=_,[_._G[_]._]=_G,},},_,(_)()
)")); )"));
} }
TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint") TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint")
@ -2449,7 +2448,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim")
{FFlag::LuauInferActualIfElseExprType, true}, {FFlag::LuauInferActualIfElseExprType, true},
// This is needed so that we don't hide the string literal free types // This is needed so that we don't hide the string literal free types
// behind a `union<_, _>` // behind a `union<_, _>`
{FFlag::LuauSimplifyOutOfLine2, true}, {FFlag::LuauSimplifyOutOfLine2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck2, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck2, true},
}; };
@ -2476,7 +2475,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim")
REQUIRE(err3); REQUIRE(err3);
CHECK_EQ("\"foo\"", toString(err3->wantedType)); CHECK_EQ("\"foo\"", toString(err3->wantedType));
CHECK_EQ("\"doge2\"", toString(err3->givenType)); CHECK_EQ("\"doge2\"", toString(err3->givenType));
} }
TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference") TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference")

View file

@ -47,7 +47,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "compatible_functions_are_unified")
}}; }};
Type functionTwo{TypeVariant{FunctionType( Type functionTwo{TypeVariant{FunctionType(
arena.addTypePack({arena.freshType(getBuiltins(), globalScope->level)}), arena.addTypePack({arena.freshType(getBuiltins(), globalScope->level)}) arena.addTypePack({arena.freshType(getBuiltins(), globalScope->level)}),
arena.addTypePack({arena.freshType(getBuiltins(), globalScope->level)})
)}}; )}};
state.tryUnify(&functionTwo, &functionOne); state.tryUnify(&functionTwo, &functionOne);
@ -291,7 +292,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
{ {
TypePackId threeNumbers = TypePackId threeNumbers =
arena.addTypePack(TypePack{{getBuiltins()->numberType, getBuiltins()->numberType, getBuiltins()->numberType}, std::nullopt}); arena.addTypePack(TypePack{{getBuiltins()->numberType, getBuiltins()->numberType, getBuiltins()->numberType}, std::nullopt});
TypePackId numberAndFreeTail = arena.addTypePack(TypePack{{getBuiltins()->numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})}); TypePackId numberAndFreeTail =
arena.addTypePack(TypePack{{getBuiltins()->numberType}, arena.addTypePack(TypePackVar{FreeTypePack{TypeLevel{}}})});
CHECK(state.canUnify(numberAndFreeTail, threeNumbers).empty()); CHECK(state.canUnify(numberAndFreeTail, threeNumbers).empty());
} }

View file

@ -710,9 +710,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring")
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true}};
{FFlag::LuauSolverV2, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict

View file

@ -633,7 +633,11 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
u.options.push_back(badCyclicUnionTy); u.options.push_back(badCyclicUnionTy);
u.options.push_back(arena.addType(TableType{ u.options.push_back(arena.addType(TableType{
{}, TableIndexer{getBuiltins()->numberType, getBuiltins()->numberType}, TypeLevel{}, getFrontend().globals.globalScope.get(), TableState::Sealed {},
TableIndexer{getBuiltins()->numberType, getBuiltins()->numberType},
TypeLevel{},
getFrontend().globals.globalScope.get(),
TableState::Sealed
})); }));
asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u)); asMutable(badCyclicUnionTy)->ty.emplace<UnionType>(std::move(u));

View file

@ -18,6 +18,8 @@ using namespace Luau::TypePath;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2);
struct TypePathFixture : Fixture struct TypePathFixture : Fixture
{ {
ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; ScopedFastFlag sff1{FFlag::LuauSolverV2, true};
@ -494,6 +496,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail")
TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
TypeArena& arena = getFrontend().globals.globalTypes; TypeArena& arena = getFrontend().globals.globalTypes;
unfreeze(arena); unfreeze(arena);
@ -510,6 +514,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail")
TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack")
{ {
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
TypeArena& arena = getFrontend().globals.globalTypes; TypeArena& arena = getFrontend().globals.globalTypes;
unfreeze(arena); unfreeze(arena);

View file

@ -365,6 +365,12 @@ struct VisitCountTracker final : TypeOnceVisitor
std::unordered_map<TypeId, unsigned> tyVisits; std::unordered_map<TypeId, unsigned> tyVisits;
std::unordered_map<TypePackId, unsigned> tpVisits; std::unordered_map<TypePackId, unsigned> tpVisits;
VisitCountTracker()
: TypeOnceVisitor("VisitCountTracker")
{
}
void cycle(TypeId) override {} void cycle(TypeId) override {}
void cycle(TypePackId) override {} void cycle(TypePackId) override {}

View file

@ -241,7 +241,7 @@ if not limitedstack then
function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end function recurse(n, ...) return n <= 1 and (1 + #{...}) or recurse(n-1, table.unpack(table.create(4000, 1))) + 1 end
local ok, msg = pcall(recurse, 19000) local ok, msg = pcall(recurse, 19000)
assert(not ok and string.find(msg, "not enough memory")) assert(not ok and string.find(msg, "too many results to unpack"))
end end
return('OK') return('OK')

View file

@ -382,4 +382,13 @@ do
assert(st and msg == nil) assert(st and msg == nil)
end end
do
local co = coroutine.wrap(xpcall)
co(0, coroutine.yield, 0)
local status, err = pcall(co, 0, 0, 0)
assert(status == false)
assert(err == "cannot resume dead coroutine")
end
return 'OK' return 'OK'