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();
}
} // namespace Luau

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
#pragma once
#include "Luau/Common.h"
#include "Luau/DenseHash.h"
#include "Luau/TypeFwd.h"
#include <memory>
@ -134,6 +135,10 @@ std::string dump(TypeId ty);
std::string dump(const std::optional<TypeId>& ty);
std::string dump(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 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 toStringVector(const std::vector<TypeId>& types, ToStringOptions& opts);
} // namespace Luau

View file

@ -32,8 +32,8 @@ public:
TypeIds(const TypeIds&) = default;
TypeIds& operator=(const TypeIds&) = default;
TypeIds(TypeIds&&) = default;
TypeIds& operator=(TypeIds&&) = default;
TypeIds(TypeIds&&) noexcept = default;
TypeIds& operator=(TypeIds&&) noexcept = default;
void insert(TypeId ty);
/// 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.
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(TypePackId root, const Path& path, NotNull<BuiltinTypes> builtinTypes);
std::optional<TypeOrPack> traverse(

View file

@ -73,6 +73,8 @@ struct GenericTypeVisitor
{
using Set = S;
const std::string visitorName;
Set seen;
bool skipBoundTypes = false;
int recursionCounter = 0;
@ -80,8 +82,9 @@ struct GenericTypeVisitor
GenericTypeVisitor() = default;
explicit GenericTypeVisitor(Set seen, bool skipBoundTypes = false)
: seen(std::move(seen))
explicit GenericTypeVisitor(const std::string visitorName, Set seen, bool skipBoundTypes = false)
: visitorName(visitorName)
, seen(std::move(seen))
, skipBoundTypes(skipBoundTypes)
{
}
@ -215,7 +218,7 @@ struct GenericTypeVisitor
void traverse(TypeId ty)
{
RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit};
RecursionLimiter limiter{visitorName, &recursionCounter, FInt::LuauVisitRecursionLimit};
if (visit_detail::hasSeen(seen, ty))
{
@ -527,8 +530,8 @@ struct GenericTypeVisitor
*/
struct TypeVisitor : GenericTypeVisitor<std::unordered_set<void*>>
{
explicit TypeVisitor(bool skipBoundTypes = false)
: GenericTypeVisitor{{}, skipBoundTypes}
explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes = false)
: 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.
struct TypeOnceVisitor : GenericTypeVisitor<DenseHashSet<void*>>
{
explicit TypeOnceVisitor(bool skipBoundTypes = false)
: GenericTypeVisitor{DenseHashSet<void*>{nullptr}, skipBoundTypes}
explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes = false)
: 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);
}
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))

View file

@ -771,7 +771,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
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;
}
@ -823,7 +825,8 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
TypeId actualTy = params[i + paramOffset];
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
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);
@ -845,7 +848,6 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
}
return true;
}
else
{
@ -863,7 +865,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
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;
}
@ -887,7 +891,8 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{
TypeId actualTy = params[i + paramOffset];
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
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);

View file

@ -124,7 +124,7 @@ std::optional<TypeFunctionReductionResult<TypeId>> tryDistributeTypeFunctionApp(
return std::nullopt;
}
}
} // namespace
TypeFunctionReductionResult<TypeId> notTypeFunction(
TypeId instance,
@ -1034,6 +1034,12 @@ TypeFunctionReductionResult<TypeId> eqTypeFunction(
struct FindRefinementBlockers : TypeOnceVisitor
{
DenseHashSet<TypeId> found{nullptr};
FindRefinementBlockers()
: TypeOnceVisitor("FindRefinementBlockers")
{
}
bool visit(TypeId ty, const BlockedType&) override
{
found.insert(ty);
@ -1056,7 +1062,7 @@ struct ContainsRefinableType : TypeOnceVisitor
{
bool found = false;
ContainsRefinableType()
: TypeOnceVisitor(/* skipBoundTypes */ true)
: TypeOnceVisitor("ContainsRefinableType", /* skipBoundTypes */ true)
{
}
@ -1199,7 +1205,6 @@ struct RefineTypeScrubber : public Substitution
}
return ty;
}
};
bool occurs(TypeId haystack, TypeId needle, DenseHashSet<TypeId>& seen)
@ -1431,7 +1436,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {resultTy, {}};
}
};
// refine target with each discriminant type in sequence (reverse of insertion order)
@ -1494,7 +1498,7 @@ struct CollectUnionTypeOptions : TypeOnceVisitor
DenseHashSet<TypeId> blockingTypes{nullptr};
explicit CollectUnionTypeOptions(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor(/* skipBoundTypes */ true)
: TypeOnceVisitor("CollectUnionTypeOptions", /* skipBoundTypes */ true)
, ctx(ctx)
{
}
@ -1764,7 +1768,8 @@ bool computeKeysOf_DEPRECATED(TypeId ty, Set<std::string>& result, DenseHashSet<
return false;
}
namespace {
namespace
{
/**
* Computes the keys of `ty` into `result`
@ -1847,7 +1852,7 @@ bool computeKeysOf(TypeId ty, Set<std::optional<std::string>>& result, DenseHash
return false;
}
}
} // namespace
TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
const std::vector<TypeId>& typeParams,
@ -2824,4 +2829,4 @@ const BuiltinTypeFunctions& builtinTypeFunctions()
return *result;
}
}
} // namespace Luau

View file

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

View file

@ -139,7 +139,10 @@ struct HasFreeType : TypeOnceVisitor
{
bool result = false;
HasFreeType() {}
HasFreeType()
: TypeOnceVisitor("TypeOnceVisitor")
{
}
bool visit(TypeId ty) override
{
@ -640,6 +643,11 @@ struct FindSimplificationBlockers : TypeOnceVisitor
{
bool found = false;
FindSimplificationBlockers()
: TypeOnceVisitor("FindSimplificationBlockers")
{
}
bool visit(TypeId) override
{
return !found;
@ -1026,7 +1034,7 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco
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>())
return visit(scope, s);

View file

@ -34,7 +34,6 @@ LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAGVARIABLE(LuauAvoidGenericsLeakingDuringFunctionCallCheck)
LUAU_FASTFLAGVARIABLE(LuauMissingFollowInAssignIndexConstraint)
LUAU_FASTFLAGVARIABLE(LuauRemoveTypeCallsForReadWriteProps)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauUseOrderedTypeSetsInConstraints)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying)
LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint)
LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking)
namespace Luau
{
@ -285,7 +285,8 @@ struct InstantiationQueuer : TypeOnceVisitor
Location location;
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
: solver(solver)
: TypeOnceVisitor("InstantiationQueuer")
, solver(solver)
, scope(scope)
, location(location)
{
@ -432,7 +433,8 @@ void ConstraintSolver::run()
{
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);
}
}
@ -633,8 +635,7 @@ void ConstraintSolver::finalizeTypeFunctions()
if (get<TypeFunctionInstanceType>(ty))
{
TypeFunctionContext context{NotNull{this}, constraint->scope, NotNull{constraint}};
FunctionGraphReductionResult result =
reduceTypeFunctions(t, constraint->location, NotNull{&context}, true);
FunctionGraphReductionResult result = reduceTypeFunctions(t, constraint->location, NotNull{&context}, true);
for (TypeId r : result.reducedTypes)
unblock(r, constraint->location);
@ -663,7 +664,8 @@ struct TypeSearcher : TypeVisitor
}
explicit TypeSearcher(TypeId needle, Polarity initialPolarity)
: needle(needle)
: TypeVisitor("TypeSearcher")
, needle(needle)
, current(initialPolarity)
{
}
@ -1195,7 +1197,8 @@ struct InfiniteTypeFinder : TypeOnceVisitor
bool foundInfiniteType = false;
explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull<Scope> scope)
: solver(solver)
: TypeOnceVisitor("InfiniteTypeFinder")
, solver(solver)
, signature(signature)
, scope(scope)
{
@ -1235,7 +1238,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
auto cTarget = follow(c.target);
LUAU_ASSERT(get<PendingExpansionType>(cTarget));
// 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);
bind(constraint, cTarget, builtinTypes->errorType);
@ -1656,6 +1659,11 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor
bool found = false;
ContainsGenerics_DEPRECATED()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED")
{
}
bool visit(TypeId ty) override
{
return !found;
@ -1736,7 +1744,8 @@ struct ContainsGenerics : public TypeOnceVisitor
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
)
{
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit};
RecursionLimiter _rl{"ConstraintSolver::tryDispatchHasIndexer", &recursionDepth, FInt::LuauSolverRecursionLimit};
subjectType = follow(subjectType);
indexType = follow(indexType);
@ -2315,6 +2324,11 @@ struct BlockedTypeFinder : TypeOnceVisitor
{
std::optional<TypeId> blocked;
BlockedTypeFinder()
: TypeOnceVisitor("ContainsGenerics_DEPRECATED")
{
}
bool visit(TypeId ty) override
{
// If we've already found one, stop traversing.
@ -2873,7 +2887,7 @@ struct FindAllUnionMembers : TypeOnceVisitor
TypeIds blockedTys;
FindAllUnionMembers()
: TypeOnceVisitor(/* skipBoundTypes */ true)
: TypeOnceVisitor("FindAllUnionMembers", /* skipBoundTypes */ true)
{
}
@ -2959,7 +2973,7 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor
bool found = false;
explicit ContainsAnyGeneric()
: TypeOnceVisitor(true)
: TypeOnceVisitor("ContainsAnyGeneric", /* skipBoundTypes */ true)
{
}
@ -2971,6 +2985,9 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor
bool visit(TypePackId ty) override
{
if (FFlag::LuauContainsAnyGenericFollowBeforeChecking)
found = found || is<GenericTypePack>(follow(ty));
else
found = found || is<GenericTypePack>(ty);
return !found;
}
@ -3367,7 +3384,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
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))
{
@ -3654,7 +3672,8 @@ struct Blocker : TypeOnceVisitor
bool blocked = false;
explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint)
: solver(solver)
: TypeOnceVisitor("Blocker")
, solver(solver)
, 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
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau
@ -262,8 +261,7 @@ declare buffer: {
)BUILTIN_SRC";
static const char* const kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC(
static const char* const kBuiltinDefinitionVectorSrc = 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
declare extern type vector with
@ -291,35 +289,6 @@ declare 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";
std::string getBuiltinDefinitionSource()

View file

@ -81,7 +81,9 @@ struct IndexerIndexCollector : public TypeOnceVisitor
{
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;
}
};
struct IndexCollector : public TypeOnceVisitor
@ -108,7 +109,9 @@ struct IndexCollector : public TypeOnceVisitor
NotNull<TypeArena> arena;
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;
}
};
}
} // namespace
bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr)
{
@ -156,17 +158,11 @@ bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr)
ic.traverse(*ty);
if (ic.indexes.size() > 1)
{
applyExpectedType(
arena->addType(UnionType{ic.indexes.take()}),
expr->index
);
applyExpectedType(arena->addType(UnionType{ic.indexes.take()}), expr->index);
}
else if (ic.indexes.size() == 1)
{
applyExpectedType(
*ic.indexes.begin(),
expr->index
);
applyExpectedType(*ic.indexes.begin(), expr->index);
}
}

View file

@ -623,7 +623,6 @@ struct UsageFinder : public AstVisitor
}
else
localBindingsReferenced.emplace_back(dfg->getDef(local), local->local);
}
return true;
}

View file

@ -1016,7 +1016,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
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;
@ -1368,6 +1369,11 @@ ModulePtr check(
struct InternalTypeFinder : TypeOnceVisitor
{
InternalTypeFinder()
: TypeOnceVisitor("InternalTypeFinder")
{
}
bool visit(TypeId, const ExternType&) override
{
return false;

View file

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

View file

@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauNormalizationIntersectTablesPreservesExternTypes)
LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver)
LUAU_FASTFLAGVARIABLE(LuauNormalizationLimitTyvarUnionSize)
namespace Luau
{
@ -1530,6 +1531,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali
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++)
{
TypeId tyvar = it->first;

View file

@ -11,7 +11,7 @@
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
namespace Luau
{
@ -247,9 +247,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
)
{
TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits};
FunctionGraphReductionResult result = reduceTypeFunctions(
fnTy, callLoc, NotNull{&context}, /*force=*/true
);
FunctionGraphReductionResult result = reduceTypeFunctions(fnTy, callLoc, NotNull{&context}, /*force=*/true);
if (!result.errors.empty())
return {OverloadIsNonviable, result.errors};
@ -330,7 +328,7 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
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 :: ...
// and superPath matches Args :: ...
@ -405,15 +403,15 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
: argExprs->size() != 0 ? argExprs->back()->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_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypeId> failedSuperTy =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
maybeEmplaceError(&errors, argLocation, &reason, 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]
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_DEPRECATED(fnTy, reason.subPath, builtinTypes);
std::optional<TypePackId> failedSuperPack =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena)
: traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes);

View file

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

View file

@ -23,6 +23,7 @@ LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint)
LUAU_FASTFLAG(LuauRefineTablesWithReadType)
LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate)
LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion)
namespace Luau
{
@ -1426,7 +1427,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
{
RecursionLimiter rl(&recursionDepth, 15);
RecursionLimiter rl("TypeSimplifier::intersect", &recursionDepth, 15);
left = simplify(left);
right = simplify(right);
@ -1442,6 +1443,10 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
return right;
if (get<UnknownType>(right) && !get<ErrorType>(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))
return arena->addType(UnionType{{right, builtinTypes->errorType}});
if (get<AnyType>(right))
@ -1514,7 +1519,7 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right)
TypeId TypeSimplifier::union_(TypeId left, TypeId right)
{
RecursionLimiter rl(&recursionDepth, 15);
RecursionLimiter rl("TypeSimplifier::union", &recursionDepth, 15);
left = simplify(left);
right = simplify(right);
@ -1602,7 +1607,7 @@ TypeId TypeSimplifier::simplify(TypeId ty)
TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
{
RecursionLimiter limiter(&recursionDepth, 60);
RecursionLimiter limiter("TypeSimplifier::simplify", &recursionDepth, 60);
ty = follow(ty);

View file

@ -2,6 +2,7 @@
#include "Luau/Subtyping.h"
#include "iostream"
#include "Luau/Common.h"
#include "Luau/Error.h"
#include "Luau/Normalize.h"
@ -21,7 +22,7 @@ LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2)
namespace Luau
{
@ -78,7 +79,7 @@ static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const Subtyp
template<typename TID>
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)
return;
@ -513,7 +514,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope
* cacheable.
*/
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
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);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
if (!env.mappedGenericPacks.empty())
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};
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && !env.mappedGenericPacks.empty())
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !env.mappedGenericPacks.empty())
result.mappedGenericPacks = env.mappedGenericPacks;
if (result.isCacheable)
@ -572,6 +573,30 @@ struct SeenSetPopper
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
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});
if (cachedResult)
{
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
env.mappedGenericPacks.try_insert(genericTp, boundTp);
@ -610,7 +635,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
cachedResult = env.tryFindSubtypingResult({subTy, superTy});
if (cachedResult)
{
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks)
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))
result = isCovariantWith(env, p, scope);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -870,6 +895,20 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
subTp = follow(subTp);
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 [superHead, superTail] = flatten(superTp);
@ -908,15 +947,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
//
// <X>(X) -> () <: (T) -> ()
TypePackId superTailPack;
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
if (headSize == 0)
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 = FFlag::LuauReturnMappedGenericPacksFromSubtyping
? std::vector<TypeId>(begin(superHead) + headSize, end(superHead))
: std::vector<TypeId>(begin(superHead), begin(superHead) + headSize);
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
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 (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
const TypePack* tp = get<TypePack>(*other);
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) -> ()
TypePackId subTailPack;
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
if (headSize == 0)
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 = FFlag::LuauReturnMappedGenericPacksFromSubtyping
? std::vector<TypeId>(begin(subHead) + headSize, end(subHead))
: std::vector<TypeId>(begin(subHead), begin(subHead) + headSize);
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
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 (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
const TypePack* tp = get<TypePack>(*other);
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);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTp, superTp, result, builtinTypes, arena);
else
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);
else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -1198,7 +1271,7 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su
reasoning.variance = SubtypingVariance::Invariant;
}
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
assertReasoningValid(subTy, superTy, result, builtinTypes, arena);
else
assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes);
@ -1862,7 +1935,9 @@ SubtypingResult Subtyping::isCovariantWith(
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name)));
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
{
@ -2131,6 +2206,13 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac
if (TypePackId* m = env.getMappedPackBounds(subTp))
return *m == superTp;
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
// We shouldn't bind generic type packs to themselves
if (subTp == superTp)
return true;
}
env.mappedGenericPacks[subTp] = superTp;
return true;

View file

@ -52,7 +52,11 @@ namespace
struct FindCyclicTypes final : TypeVisitor
{
FindCyclicTypes() = default;
FindCyclicTypes()
: TypeVisitor("FindCyclicTypes")
{
}
FindCyclicTypes(const FindCyclicTypes&) = delete;
FindCyclicTypes& operator=(const FindCyclicTypes&) = delete;
@ -827,7 +831,9 @@ struct TypeStringifier
std::string openbrace = "@@@";
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:
if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification)
@ -1899,6 +1905,39 @@ std::string dump(const std::optional<TypePackId>& ty)
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)
{
auto binding = scope->linearSearchForBinding(name);

View file

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

View file

@ -37,7 +37,7 @@ LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls)
LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete)
@ -161,6 +161,11 @@ struct TypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypeId> mentionedFunctions{nullptr};
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
TypeFunctionFinder()
: TypeOnceVisitor("TypeFunctionFinder")
{
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
mentionedFunctions.insert(ty);
@ -182,6 +187,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
: TypeOnceVisitor("InternalTypeFunctionFinder")
{
TypeFunctionFinder f;
for (TypeId fn : declStack)
@ -2052,7 +2058,8 @@ void TypeChecker2::visit(AstExprFunction* fn)
TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy);
if (result.shouldRecommendAnnotation && !get<UnknownType>(result.guessedReturnType))
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);
reportError(
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
);
@ -2920,11 +2929,11 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc
continue;
std::optional<TypeOrPack> optSubLeaf =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes);
std::optional<TypeOrPack> optSuperLeaf =
FFlag::LuauReturnMappedGenericPacksFromSubtyping
FFlag::LuauReturnMappedGenericPacksFromSubtyping2
? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena)
: traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes);

View file

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

View file

@ -24,6 +24,11 @@ struct InstanceCollector2 : TypeOnceVisitor
DenseHashSet<TypeId> cyclicInstance{nullptr};
DenseHashSet<TypeId> instanceArguments{nullptr};
InstanceCollector2()
: TypeOnceVisitor("InstanceCollector2")
{
}
bool visit(TypeId ty, const TypeFunctionInstanceType& it) override
{
// 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);
}
}
} // namespace Luau

View file

@ -442,7 +442,7 @@ struct InplaceDemoter : TypeOnceVisitor
TypeArena* arena;
InplaceDemoter(TypeLevel level, TypeArena* arena)
: TypeOnceVisitor(/* skipBoundTypes= */ true)
: TypeOnceVisitor("InplaceDemoter", /* skipBoundTypes= */ true)
, newLevel(level)
, arena(arena)
{
@ -2150,7 +2150,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
for (TypeId t : utv)
{
RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit);
RecursionLimiter _rl("TypeInfer::UnionType", &recursionCount, FInt::LuauTypeInferRecursionLimit);
// Not needed when we normalize types.
if (get<AnyType>(follow(t)))
@ -2189,7 +2189,7 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
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))
parts.push_back(*ty);
@ -4129,7 +4129,9 @@ void TypeChecker::checkArgumentList(
auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack);
state.reportError(TypeError{
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;
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;
}

View file

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

View file

@ -16,7 +16,7 @@
#include <sstream>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
// 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
@ -285,7 +285,7 @@ struct TraversalState
TypeOrPack current;
NotNull<BuiltinTypes> builtinTypes;
// TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping is clipped
// TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping2 is clipped
const DenseHashMap<TypePackId, TypePackId>* mappedGenericPacks;
TypeArena* arena;
int steps = 0;
@ -417,7 +417,7 @@ struct TraversalState
{
auto currentPack = get<TypePackId>(current);
LUAU_ASSERT(currentPack);
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
{
if (const auto tp = get<TypePack>(*currentPack))
{
@ -576,7 +576,7 @@ struct TraversalState
if (auto tail = it.tail())
{
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping && mappedGenericPacks && mappedGenericPacks->contains(*tail))
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && mappedGenericPacks && mappedGenericPacks->contains(*tail))
updateCurrent(*mappedGenericPacks->find(*tail));
else
updateCurrent(*tail);
@ -595,9 +595,9 @@ struct TraversalState
if (checkInvariants())
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
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping)
if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_ASSERT(arena && mappedGenericPacks);
else if (!arena || !mappedGenericPacks)
return false;

View file

@ -33,7 +33,8 @@ struct PromoteTypeLevels final : TypeOnceVisitor
TypeLevel minLevel;
PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel)
: log(log)
: TypeOnceVisitor("PromoteTypeLevels")
, log(log)
, typeArena(typeArena)
, minLevel(minLevel)
{
@ -145,7 +146,8 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev
struct SkipCacheForType final : TypeOnceVisitor
{
SkipCacheForType(const DenseHashMap<TypeId, bool>& skipCacheForType, const TypeArena* typeArena)
: skipCacheForType(skipCacheForType)
: TypeOnceVisitor("SkipCacheForType")
, skipCacheForType(skipCacheForType)
, 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)
{
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
RecursionLimiter _ra("Unifier::tryUnify_", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
++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)
{
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
RecursionLimiter _ra("Unifier::tryUnify_", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
++sharedState.counters.iterationCount;
@ -2030,7 +2032,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{
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);
return;
}
@ -2048,7 +2050,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{
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);
}
@ -2120,7 +2122,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{
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);
return;
}
@ -2140,7 +2142,7 @@ void Unifier::tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection,
{
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);
}
@ -2732,7 +2734,7 @@ bool Unifier::occursCheck(TypeId needle, TypeId haystack, bool reversed)
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;
@ -2806,7 +2808,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
if (!log.getMutable<FreeTypePack>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericTypePack>(needle)))
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))
{

View file

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

View file

@ -39,7 +39,7 @@ private:
T oldValue;
};
}
} // namespace
struct FindUserTypeFunctionBlockers : TypeOnceVisitor
{
@ -48,7 +48,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor
std::vector<TypeId> blockingTypes;
explicit FindUserTypeFunctionBlockers(NotNull<TypeFunctionContext> ctx)
: TypeOnceVisitor(/* skipBoundTypes */ true)
: TypeOnceVisitor("FindUserTypeFunctionBlockers", /* skipBoundTypes */ true)
, 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.
// See docs/SyntaxChanges.md for an explanation.
LUAU_FASTFLAGVARIABLE(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauDeclareExternType)
LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer)
LUAU_FASTFLAGVARIABLE(LuauParseAttributeFixUninit)
LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false)
@ -1249,11 +1248,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
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;
if (FFlag::LuauDeclareExternType)
{
if (AstName(lexer.current().name) == "extern")
{
foundExtern = true;
@ -1263,22 +1260,20 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name
);
}
}
nextLexeme();
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;
if (AstName(lexer.current().name) == "extends")
{
nextLexeme();
superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name;
superName = parseName("supertype name").name;
}
if (FFlag::LuauDeclareExternType)
{
if (foundExtern)
{
if (AstName(lexer.current().name) != "with")
@ -1290,7 +1285,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
else
nextLexeme();
}
}
TempVector<AstDeclaredExternTypeProperty> props(scratchDeclaredClassProps);
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;
// 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");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
@ -1427,10 +1419,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstTableIndexer* badIndexer = parseTableIndexer(AstTableAccess::ReadWrite, std::nullopt, lexer.current()).node;
// 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");
else
report(badIndexer->location, "Cannot have more than one class indexer");
}
else
{
@ -1466,13 +1455,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
AstType* type = parseType(/* in declaration context */ true);
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
{
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

@ -18,7 +18,8 @@ inline bool isAnalysisFlagExperimental(const char* flag)
"LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly
"LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly
"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
nullptr,
};

View file

@ -26,10 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileInlineNonConstInit)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip)
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 (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});
else
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc});
}
else
{
@ -725,7 +720,7 @@ struct Compiler
{
pushLocal(arg.local, arg.reg, arg.allocpc);
if (FFlag::LuauCompileInlineNonConstInit && arg.init)
if (arg.init)
{
if (Variable* lv = variables.find(arg.local))
lv->init = arg.init;
@ -785,12 +780,9 @@ struct Compiler
if (Constant* var = locstants.find(local))
var->type = Constant::Type_Unknown;
if (FFlag::LuauCompileInlineNonConstInit)
{
if (Variable* lv = variables.find(local))
lv->init = nullptr;
}
}
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body);
}
@ -3956,7 +3948,7 @@ struct Compiler
bool visit(AstStatTypeFunction* node) override
{
return !FFlag::LuauCompileFixTypeFunctionSkip;
return false;
}
};

View file

@ -8,8 +8,6 @@
#include <limits.h>
LUAU_FASTFLAGVARIABLE(LuauCompileCostModelConstants)
namespace Luau
{
namespace Compile
@ -43,25 +41,6 @@ static uint64_t parallelMulSat(uint64_t a, int b)
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
{
static const uint64_t kLiteral = ~0ull;
@ -132,7 +111,7 @@ struct CostVisitor : AstVisitor
Cost model(AstExpr* node)
{
if (FFlag::LuauCompileCostModelConstants && constants.contains(node))
if (constants.contains(node))
return Cost(0, Cost::kLiteral);
if (AstExprGroup* expr = node->as<AstExprGroup>())
@ -280,17 +259,8 @@ struct CostVisitor : AstVisitor
int tripCount = -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);
}
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);
return false;

View file

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

View file

@ -15,6 +15,8 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauSafeStackCheck, false)
/*
* This file contains most implementations of core Lua APIs from lua.h.
*
@ -138,8 +140,37 @@ int lua_checkstack(lua_State* L, int size)
if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK)
res = 0; // stack overflow
else if (size > 0)
{
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);
}
return res;

View file

@ -17,6 +17,8 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauErrorYield, false)
// keep max stack allocation request under 1GB
#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
}
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.
** The arguments are on the stack, right after the function.
@ -277,6 +296,12 @@ void luaD_call(lua_State* L, StkId func, int nresults)
ptrdiff_t funcoffset = savestack(L, func);
ptrdiff_t cioffset = saveci(L, L->ci);
if (DFFlag::LuauErrorYield)
{
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
@ -290,6 +315,7 @@ void luaD_call(lua_State* L, StkId func, int nresults)
if (!oldactive)
L->isactive = false;
}
}
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);
}
// 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)
{
switch (errcode)
@ -595,6 +642,10 @@ static void callerrfunc(lua_State* L, void* ud)
setobj2s(L, L->top, L->top - 1);
setobj2s(L, L->top - 1, errfunc);
incr_top(L);
if (DFFlag::LuauErrorYield)
luaD_callny(L, L->top - 2, 1);
else
luaD_call(L, L->top - 2, 1);
}

View file

@ -10,15 +10,16 @@
// returns target stack for 'n' extra elements to reallocate
// if possible, stack size growth factor is 2x
#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) \
if ((char*)L->stack_last - (char*)L->top <= (n) * (int)sizeof(TValue)) \
if (stacklimitreached(L, (n))) \
luaD_reallocstack(L, getgrownstacksize(L, (n)), 1); \
else \
condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 1));
#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); \
else \
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 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 void luaD_reallocCI(lua_State* L, int newsize);
LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci);

View file

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

View file

@ -2,28 +2,200 @@
#include "luau.pb.h"
static const std::string kNames[] = {
"_G", "_VERSION", "__add", "__call", "__concat", "__div", "__eq", "__idiv", "__index",
"__iter", "__le", "__len", "__lt", "__mod", "__mode", "__mul", "__namecall", "__newindex",
"__pow", "__sub", "__type", "__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",
"_G",
"_VERSION",
"__add",
"__call",
"__concat",
"__div",
"__eq",
"__idiv",
"__index",
"__iter",
"__le",
"__len",
"__lt",
"__mod",
"__mode",
"__mul",
"__namecall",
"__newindex",
"__pow",
"__sub",
"__type",
"__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[] = {
@ -46,25 +218,8 @@ static const std::string kExternTypes[] = {
};
static const std::string kBuiltinTypes[] = {
"len",
"unm",
"add",
"sub",
"mul",
"div",
"idiv",
"pow",
"mod",
"concat",
"lt",
"le",
"eq",
"keyof",
"rawkeyof",
"index",
"rawget",
"setmetatable",
"getmetatable",
"len", "unm", "add", "sub", "mul", "div", "idiv", "pow", "mod", "concat",
"lt", "le", "eq", "keyof", "rawkeyof", "index", "rawget", "setmetatable", "getmetatable",
};
struct ProtoToLuau

View file

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

View file

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

View file

@ -34,9 +34,13 @@ void luaC_validate(lua_State* L);
// internal functions, declared in lvm.h - not exposed via lua.h
void luau_callhook(lua_State* L, lua_Hook hook, void* userdata);
LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_DYNAMIC_FASTFLAG(LuauErrorYield)
LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck)
static lua_CompileOptions defaultOptions()
{
@ -754,6 +758,8 @@ TEST_CASE("Closure")
TEST_CASE("Calls")
{
ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true};
runConformance("calls.luau");
}
@ -816,6 +822,8 @@ TEST_CASE("UTF8")
TEST_CASE("Coroutine")
{
ScopedFastFlag luauErrorYield{DFFlag::LuauErrorYield, true};
runConformance("coroutine.luau");
}
@ -2094,13 +2102,33 @@ int slowlyOverflowStack(lua_State* L)
TEST_CASE("ApiStack")
{
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true};
StateRef globalState(lua_newstate(blockableRealloc, nullptr), lua_close);
lua_State* GL = globalState.get();
{
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")
@ -2315,6 +2343,8 @@ TEST_CASE("StringConversion")
TEST_CASE("GCDump")
{
ScopedFastFlag luauHeapDumpStringSizeOverhead{FFlag::LuauHeapDumpStringSizeOverhead, true};
// 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_enumheap(
@ -2362,14 +2392,15 @@ local function f()
x[1] = math.abs(42)
end
function foo()
coroutine.yield()
x[2] = ''
for i = 1, 10000 do x[2] ..= '1234567890' end
end
foo()
return f
)");
lua_pushstring(CL, "=GCDump");
lua_loadstring(CL);
lua_resume(CL, nullptr, 0);
REQUIRE(lua_loadstring(CL) == 1);
REQUIRE(lua_resume(CL, nullptr, 0) == LUA_OK);
#ifdef _WIN32
const char* path = "NUL";
@ -2380,6 +2411,7 @@ return f
FILE* f = fopen(path, "w");
REQUIRE(f);
luaC_fullgc(L);
luaC_dump(L, f, nullptr);
fclose(f);
@ -2395,14 +2427,10 @@ return f
struct EnumContext
{
EnumContext()
: nodes{nullptr}
, edges{nullptr}
{
}
Luau::DenseHashMap<void*, Node> nodes{nullptr};
Luau::DenseHashMap<void*, void*> edges{nullptr};
Luau::DenseHashMap<void*, Node> nodes;
Luau::DenseHashMap<void*, void*> edges;
bool seenTargetString = false;
} ctx;
luaC_enumheap(
@ -2425,6 +2453,14 @@ return f
else if (tt == LUA_TTHREAD)
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 : ""};
},
@ -2437,6 +2473,7 @@ return f
CHECK(!ctx.nodes.empty());
CHECK(!ctx.edges.empty());
CHECK(ctx.seenTargetString);
}
TEST_CASE("Interrupt")
@ -3296,6 +3333,7 @@ TEST_CASE("HugeFunctionLoadFailure")
REQUIRE_EQ(largeAllocationToFail, expectedTotalLargeAllocations);
}
TEST_CASE("IrInstructionLimit")
{
if (!codegen || !luau_codegen_supported())

View file

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

View file

@ -5,8 +5,6 @@
#include "doctest.h"
LUAU_FASTFLAG(LuauCompileCostModelConstants)
using namespace Luau;
namespace Luau
@ -133,37 +131,6 @@ end
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")
{
uint64_t model = modelFunction(R"(

View file

@ -135,7 +135,9 @@ TEST_CASE_FIXTURE(ESFixture, "string | never")
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")
@ -161,9 +163,9 @@ TEST_CASE_FIXTURE(ESFixture, "never & string")
TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)")
{
CHECK(
"string" == simplifyStr(arena->addType(
IntersectionType{{getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}}
))
"string" == simplifyStr(arena->addType(IntersectionType{
{getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}
}))
);
}
@ -386,7 +388,8 @@ TEST_CASE_FIXTURE(ESFixture, "union<number, number>")
{
CHECK(
"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)")
{
const TypeId t1 = arena->addType(
UnionType{{getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType}}
);
const TypeId t1 = arena->addType(UnionType{
{getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType}
});
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")
{
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,7 +696,8 @@ Frontend& Fixture::getFrontend()
&fileResolver,
&configResolver,
FrontendOptions{
/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed}
/* retainFullTypeGraphs= */ true, /* forAutocomplete */ false, /* runLintChecks */ false, /* randomConstraintResolutionSeed */ randomSeed
}
);
builtinTypes = f.builtinTypes;
@ -732,7 +733,6 @@ Frontend& Fixture::getFrontend()
BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete)
: Fixture(prepareAutocomplete)
{
}
Frontend& BuiltinsFixture::getFrontend()

View file

@ -185,8 +185,10 @@ struct Fixture
// TODO: test theory about dynamic dispatch
NotNull<BuiltinTypes> getBuiltins();
virtual Frontend& getFrontend();
private:
bool hasDumpedErrors = false;
protected:
bool forAutocomplete = false;
std::optional<Frontend> frontend;

View file

@ -238,7 +238,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
ParseResult parseResult = parseHelper(document);
FrontendOptions options;
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()
@ -3792,11 +3792,17 @@ if result.type == "ok" then
result.
end
)";
autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){
autocompleteFragmentInOldSolver(
source,
dest,
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")
@ -3825,11 +3831,17 @@ if result.type == "err" then
end
)";
autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){
autocompleteFragmentInOldSolver(
source,
dest,
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")
@ -3858,11 +3870,17 @@ if result.type == "ok" then
result.
end
)";
autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){
autocompleteFragmentInNewSolver(
source,
dest,
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")
@ -3891,11 +3909,17 @@ if result.type == "err" then
end
)";
autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){
autocompleteFragmentInNewSolver(
source,
dest,
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")

View file

@ -623,7 +623,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "produce_errors_for_unchanged_file_with_error
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");
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);
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);
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["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")

View file

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

View file

@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit)
LUAU_FASTINT(LuauParseErrorLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauParseStringIndexer)
LUAU_FASTFLAG(LuauDeclareExternType)
LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix)
// Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix
@ -1999,8 +1998,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_class_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
{
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
AstStatBlock* stat = parseEx(R"(
declare extern type Foo with
prop: number
@ -2051,8 +2048,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
{
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
ParseResult result = tryParse(R"(
declare extern type Foo
prop: number
@ -2108,8 +2103,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
{
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
AstStatBlock* stat = parseEx(R"(
declare extern type Foo with
prop: number
@ -2160,8 +2153,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with")
{
ScopedFastFlag sff{FFlag::LuauDeclareExternType, true};
ParseResult result = tryParse(R"(
declare extern type Foo
prop: number
@ -2281,7 +2272,7 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer")
[number]: number
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);

View file

@ -19,6 +19,9 @@ using namespace Luau;
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
struct LimitFixture : BuiltinsFixture
{
@ -47,7 +50,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type")
{
DOES_NOT_PASS_NEW_SOLVER_GUARD();
constexpr const char* src = R"LUA(
constexpr const char* src = R"LUAU(
--!strict
-- 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 {
Result = Result,
}
)LUA";
)LUAU";
CheckResult result = check(src);
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();

View file

@ -9,6 +9,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSimplifyAnyAndUnion)
LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit)
namespace
@ -62,6 +63,7 @@ struct SimplifyFixture : Fixture
TypeId anotherChildClassTy = nullptr;
TypeId unrelatedClassTy = nullptr;
// This only affects type stringification.
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
SimplifyFixture()
@ -619,4 +621,26 @@ TEST_CASE_FIXTURE(SimplifyFixture, "cyclic_never_union_and_string")
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();

View file

@ -17,7 +17,7 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
using namespace Luau;
@ -1240,11 +1240,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: <T>(T...) -> ()")
TEST_CASE_FIXTURE(SubtypeFixture, "bill")
{
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{
{{"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);
@ -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")
{
ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
TypeId longTy = arena.addType(UnionType{
{getBuiltins()->booleanType,
@ -1631,8 +1639,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
TypeId bTy = arena.addType(GenericType{"B"});
getMutable<GenericType>(bTy)->scope = moduleScope.get();
TypeId genericFunctionTy =
arena.addType(FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, getBuiltins()->truthyType), bTy)})}
TypeId genericFunctionTy = arena.addType(
FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, getBuiltins()->truthyType), bTy)})}
);
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};
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));
}

View file

@ -744,10 +744,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161")
if (!FFlag::LuauSolverV2)
return;
ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization4, true},
{FFlag::LuauStuckTypeFunctionsStillDispatch, true}
};
ScopedFastFlag sff[] = {{FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauStuckTypeFunctionsStillDispatch, true}};
CheckResult result = check(R"(
local EnumVariants = {
@ -1787,7 +1784,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_prop
LUAU_REQUIRE_NO_ERRORS(results);
CHECK_EQ(R"("" | "one")", toString(requireTypeAlias("FoobarKeys")));
CHECK_EQ(R"("" | "two")", toString(requireTypeAlias("TableKeys")));
}
struct TFFixture
@ -1796,7 +1792,10 @@ struct TFFixture
NotNull<TypeArena> arena{&arena_};
BuiltinTypes builtinTypes_;
NotNull<BuiltinTypes> getBuiltins(){ return NotNull{&builtinTypes_};}
NotNull<BuiltinTypes> getBuiltins()
{
return NotNull{&builtinTypes_};
}
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);
CHECK(
toString(result.errors[1]) ==
R"(Type function instance get<> is uninhabited)"
);
CHECK(toString(result.errors[1]) == R"(Type function instance get<> is uninhabited)");
}
TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block")

View file

@ -10,7 +10,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
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")
{
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
// This used to crash under the new solver: we would like this to continue
// to not crash.
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")
{
ScopedFastFlag _{FFlag::LuauGuardAgainstMalformedTypeAliasExpansion2, true};
LUAU_REQUIRE_ERRORS(check(R"(
export type t138 = t0<t138>
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);
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
@ -447,7 +448,8 @@ TEST_CASE_FIXTURE(Fixture, "self_referential_type_alias")
if (FFlag::LuauRemoveTypeCallsForReadWriteProps)
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);
std::optional<TypeId> firstArg = first(incrFunc->argTypes);

View file

@ -9,7 +9,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck2)
LUAU_FASTFLAG(LuauIntersectNotNil)
LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts)
@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauStuckTypeFunctionsStillDispatch)
LUAU_FASTFLAG(LuauRemoveTypeCallsForReadWriteProps)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking)
using namespace Luau;
@ -1004,7 +1005,7 @@ local TheDispatcher: Dispatcher = {
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few")
{
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"(
function test(a: number)
@ -1032,7 +1033,7 @@ wrapper(test)
TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many")
{
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"(
function test2(a: number, b: string)
@ -1076,7 +1077,7 @@ wrapper(test2, 1, "")
TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return")
{
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"(
function test2(a: number)
@ -1106,7 +1107,7 @@ wrapper(test2, 1)
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"(
function test2(a: number)
@ -1124,7 +1125,7 @@ wrapper(test2, "hello")
TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs")
{
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping, true};
ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true};
CheckResult result = check(R"(
function test2(a: number)
@ -1851,4 +1852,63 @@ end
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();

View file

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

View file

@ -13,7 +13,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization4)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping)
LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2)
using namespace Luau;
@ -823,7 +823,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any")
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"(
function test2(a: number, b: string)

View file

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

View file

@ -127,7 +127,8 @@ struct RefinementExternTypeFixture : BuiltinsFixture
};
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 = {
{"Part0", 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"(
--!strict

View file

@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr)
LUAU_FASTFLAG(LuauInferActualIfElseExprType)
LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex)
LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement)
LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize)
TEST_SUITE_BEGIN("TableTests");
@ -6129,7 +6130,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_119126_regression")
)");
LUAU_REQUIRE_ERROR_COUNT(2, results);
for (const auto& err: results.errors)
for (const auto& err : results.errors)
{
auto e = get<TypeMismatch>(err);
REQUIRE(e);
@ -6173,5 +6174,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assert
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();

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")
{
ScopedFastFlag _ {FFlag::LuauSolverV2, true};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// This is the original fuzzer version of the above issue.
CheckResult results = check(R"(
local function l0()
@ -2383,7 +2383,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free")
local l249 = require(module0)
_,_ = {[`{_}`]=_,[_._G._]=(_)(),[_["" + _]._G]={_=_,_=_,[_._G[_]._]=_G,},},_,(_)()
)"));
}
TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint")
@ -2476,7 +2475,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim")
REQUIRE(err3);
CHECK_EQ("\"foo\"", toString(err3->wantedType));
CHECK_EQ("\"doge2\"", toString(err3->givenType));
}
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(
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);
@ -291,7 +292,8 @@ TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly")
{
TypePackId threeNumbers =
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());
}

View file

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

View file

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

View file

@ -365,6 +365,12 @@ struct VisitCountTracker final : TypeOnceVisitor
std::unordered_map<TypeId, unsigned> tyVisits;
std::unordered_map<TypePackId, unsigned> tpVisits;
VisitCountTracker()
: TypeOnceVisitor("VisitCountTracker")
{
}
void cycle(TypeId) 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
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
return('OK')

View file

@ -382,4 +382,13 @@ do
assert(st and msg == nil)
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'