Sync to upstream/release/674 (#1832)

# General

* Expose an optional `get_alias` API as an alternative to `get_config`
in Luau.Require and Luau.RequireNavigator.
* Improve the Luau CLI's virtual filesystem implementation to fix bugs
related to `init.luau`. Fixes
https://github.com/luau-lang/luau/issues/1816


# New Type Solver

* Avoid double reporting errors when erroneous arguments are provided to
type functions.
* Fix some instances of unresovable cyclic type functions in loops by
only considering the first loop cycles. This results in some type
inference inaccuracies when the type of a variable in loop through
multiple iterations. Fixes
https://github.com/luau-lang/luau/issues/1413.
* Better generalize free types that have meaningful lower and upper
bounds, especially for table indexers.
* Report more specific errors when assigning or returning table literal
types, instead of citing the *entire* table type.
* Inference for functions with generic type packs is greatly improved.
* Fix some internal compiler exceptions when using type-stating
functions like `table.freeze` in `if _ then _ else _` expressions and
short circuiting binary operations.
* More consistently simplify unions of primitive types, especially in
array-like and dictionary-like tables.
* Fix a crash when type checking an erroneous type alias containing
`typeof` with a type assertion expression, as in:
  ```
  type MyTable = {}
  -- This will error at type checking time as it's a duplicate
  type MyTable = typeof(setmetatable(SomeTable :: {}, SomeMetaTable));
  ```
* Fix a crash when inferring the type of an index expression where the
indexee is invalid (e.g. `nil`).

# Runtime
* Avoid throwing an exception from `luau_load` if we run out of memory.
* Type functions are no longer compiled and included in bytecode. Fixes
#1817.
* Fix some instances of Luau C API functions reading invalid debug
information (generally when the first or last instruction of a block was
being inspected). Fixes #1369.
* Avoid potential signed integer overflow when doing bounds checks on
tables.
* Support 16 byte aligned userdata objects when system allocation
alignment is also 16 bytes.
* Fix memory leaks in `Luau.Require` when using VM build with no
exceptions. Fixes #1827.

---------

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: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Varun Saini <vsaini@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
Hunter Goldstein 2025-05-16 12:39:58 -07:00 committed by GitHub
parent 3eb0c13678
commit 92cce5776c
Signed by: DevComp
GPG key ID: B5690EEEBB952194
129 changed files with 4607 additions and 4575 deletions

View file

@ -9,7 +9,7 @@
namespace Luau namespace Luau
{ {
static constexpr char kRequireTagName[] = "require"; inline constexpr char kRequireTagName[] = "require";
struct Frontend; struct Frontend;
struct GlobalTypes; struct GlobalTypes;

View file

@ -276,6 +276,12 @@ struct ReducePackConstraint
TypePackId tp; TypePackId tp;
}; };
// simplify ty
struct SimplifyConstraint
{
TypeId ty;
};
using ConstraintV = Variant< using ConstraintV = Variant<
SubtypeConstraint, SubtypeConstraint,
PackSubtypeConstraint, PackSubtypeConstraint,
@ -294,7 +300,8 @@ using ConstraintV = Variant<
ReduceConstraint, ReduceConstraint,
ReducePackConstraint, ReducePackConstraint,
EqualityConstraint, EqualityConstraint,
TableCheckConstraint>; TableCheckConstraint,
SimplifyConstraint>;
struct Constraint struct Constraint
{ {

View file

@ -173,6 +173,8 @@ private:
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes; std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
std::vector<InteriorFreeTypes> interiorFreeTypes; std::vector<InteriorFreeTypes> interiorFreeTypes;
std::vector<TypeId> unionsToSimplify;
/** /**
* Fabricates a new free type belonging to a given scope. * Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to. * @param scope the scope the free type belongs to.
@ -447,6 +449,12 @@ private:
// make a union type function of these two types // make a union type function of these two types
TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); TypeId makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
// Make a union type and add it to `unionsToSimplify`, ensuring that
// later we will attempt to simplify this union in order to keep types
// small.
TypeId makeUnion(std::vector<TypeId> options);
// make an intersect type function of these two types // make an intersect type function of these two types
TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs); TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
void prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program); void prepopulateGlobalScopeForFragmentTypecheck(const ScopePtr& globalScope, const ScopePtr& resumeScope, AstStatBlock* program);

View file

@ -249,6 +249,8 @@ public:
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint); bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do // for a, ... in some_table do
// also handles __iter metamethod // also handles __iter metamethod
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force); bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);

View file

@ -462,6 +462,11 @@ struct ReservedIdentifier
bool operator==(const ReservedIdentifier& rhs) const; bool operator==(const ReservedIdentifier& rhs) const;
}; };
struct UnexpectedArrayLikeTableItem
{
bool operator==(const UnexpectedArrayLikeTableItem&) const { return true; }
};
using TypeErrorData = Variant< using TypeErrorData = Variant<
TypeMismatch, TypeMismatch,
UnknownSymbol, UnknownSymbol,
@ -512,7 +517,8 @@ using TypeErrorData = Variant<
UnexpectedTypePackInSubtyping, UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended, ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError, UserDefinedTypeFunctionError,
ReservedIdentifier>; ReservedIdentifier,
UnexpectedArrayLikeTableItem>;
struct TypeErrorSummary struct TypeErrorSummary
{ {

View file

@ -8,7 +8,7 @@
namespace Luau namespace Luau
{ {
static const std::unordered_map<AstExprBinary::Op, const char*> kBinaryOpMetamethods{ inline const std::unordered_map<AstExprBinary::Op, const char*> kBinaryOpMetamethods{
{AstExprBinary::Op::CompareEq, "__eq"}, {AstExprBinary::Op::CompareEq, "__eq"},
{AstExprBinary::Op::CompareNe, "__eq"}, {AstExprBinary::Op::CompareNe, "__eq"},
{AstExprBinary::Op::CompareGe, "__lt"}, {AstExprBinary::Op::CompareGe, "__lt"},
@ -25,7 +25,7 @@ static const std::unordered_map<AstExprBinary::Op, const char*> kBinaryOpMetamet
{AstExprBinary::Op::Concat, "__concat"}, {AstExprBinary::Op::Concat, "__concat"},
}; };
static const std::unordered_map<AstExprUnary::Op, const char*> kUnaryOpMetamethods{ inline const std::unordered_map<AstExprUnary::Op, const char*> kUnaryOpMetamethods{
{AstExprUnary::Op::Minus, "__unm"}, {AstExprUnary::Op::Minus, "__unm"},
{AstExprUnary::Op::Len, "__len"}, {AstExprUnary::Op::Len, "__len"},
}; };

View file

@ -60,7 +60,7 @@ struct SubtypingReasoningHash
}; };
using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>; using SubtypingReasonings = DenseHashSet<SubtypingReasoning, SubtypingReasoningHash>;
static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}; inline const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid};
struct SubtypingResult struct SubtypingResult
{ {

View file

@ -1206,7 +1206,7 @@ std::vector<TypeId> filterMap(TypeId type, TypeIdPredicate predicate);
// A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`. // A tag to mark a type which doesn't derive directly from the root type as overriding the return of `typeof`.
// Any classes which derive from this type will have typeof return this type. // Any classes which derive from this type will have typeof return this type.
static constexpr char kTypeofRootTag[] = "typeofRoot"; inline constexpr char kTypeofRootTag[] = "typeofRoot";
void attachTag(TypeId ty, const std::string& tagName); void attachTag(TypeId ty, const std::string& tagName);
void attachTag(Property& prop, const std::string& tagName); void attachTag(Property& prop, const std::string& tagName);

View file

@ -193,6 +193,9 @@ private:
void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& result); void explainError(TypeId subTy, TypeId superTy, Location location, const SubtypingResult& result);
void explainError(TypePackId subTy, TypePackId superTy, Location location, const SubtypingResult& result); void explainError(TypePackId subTy, TypePackId superTy, Location location, const SubtypingResult& result);
bool testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedTy);
bool testIsSubtype(TypeId subTy, TypeId superTy, Location location); bool testIsSubtype(TypeId subTy, TypeId superTy, Location location);
bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location); bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location);
void reportError(TypeError e); void reportError(TypeError e);

View file

@ -216,9 +216,6 @@ struct TypeFunctionExternType
std::optional<TypeFunctionTypeId> metatable; // metaclass? std::optional<TypeFunctionTypeId> metatable; // metaclass?
// this was mistaken, and we should actually be keeping separate read/write types here.
std::optional<TypeFunctionTypeId> parent_DEPRECATED;
std::optional<TypeFunctionTypeId> readParent; std::optional<TypeFunctionTypeId> readParent;
std::optional<TypeFunctionTypeId> writeParent; std::optional<TypeFunctionTypeId> writeParent;

View file

@ -183,7 +183,7 @@ struct PathHash
}; };
/// The canonical "empty" Path, meaning a Path with no components. /// The canonical "empty" Path, meaning a Path with no components.
static const Path kEmpty{}; inline const Path kEmpty{};
struct PathBuilder struct PathBuilder
{ {

View file

@ -291,4 +291,14 @@ void trackInteriorFreeType(Scope* scope, TypeId ty);
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp); void trackInteriorFreeTypePack(Scope* scope, TypePackId tp);
// A fast approximation of subTy <: superTy
bool fastIsSubtype(TypeId subTy, TypeId superTy);
/**
* @param tables A list of potential table parts of a union
* @param exprType Type of the expression to match
* @return An element of `tables` that best matches `exprType`.
*/
std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, TypeId exprType, NotNull<BuiltinTypes> builtinTypes);
} // namespace Luau } // namespace Luau

View file

@ -25,7 +25,6 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows) LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
@ -490,14 +489,11 @@ static void autocompleteProps(
// t1 where t1 = t1 | ExternType // t1 where t1 = t1 | ExternType
// //
// Then we are on a one way journey to a stack overflow. // Then we are on a one way journey to a stack overflow.
if (FFlag::LuauAutocompleteUnionCopyPreviousSeen)
{
for (auto ty : seen) for (auto ty : seen)
{ {
if (is<UnionType, IntersectionType>(ty)) if (is<UnionType, IntersectionType>(ty))
innerSeen.insert(ty); innerSeen.insert(ty);
} }
}
if (isNil(*iter)) if (isNil(*iter))
{ {

View file

@ -30,9 +30,8 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
@ -292,8 +291,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
static void finalizeGlobalBindings(ScopePtr scope) static void finalizeGlobalBindings(ScopePtr scope)
{ {
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
for (const auto& pair : scope->bindings) for (const auto& pair : scope->bindings)
{ {
persist(pair.second.typeId); persist(pair.second.typeId);
@ -313,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2 Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization3
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
globalScope = globals.globalScope.get(); globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -420,23 +417,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on // clang-format on
} }
if (FFlag::LuauUserTypeFunTypecheck)
{
finalizeGlobalBindings(globals.globalScope); finalizeGlobalBindings(globals.globalScope);
}
else
{
for (const auto& pair : globals.globalScope->bindings)
{
persist(pair.second.typeId);
if (TableType* ttv = getMutable<TableType>(pair.second.typeId))
{
if (!ttv->name)
ttv->name = "typeof(" + toString(pair.first) + ")";
}
}
}
attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>()); attachMagicFunction(getGlobalBinding(globals, "assert"), std::make_shared<MagicAssert>());
@ -500,8 +481,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
attachTag(requireTy, kRequireTagName); attachTag(requireTy, kRequireTagName);
attachMagicFunction(requireTy, std::make_shared<MagicRequire>()); attachMagicFunction(requireTy, std::make_shared<MagicRequire>());
if (FFlag::LuauUserTypeFunTypecheck)
{
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder // Global scope cannot be the parent of the type checking environment because it can be changed by the embedder
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings; globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames; globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
@ -552,7 +531,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
finalizeGlobalBindings(globals.globalTypeFunctionScope); finalizeGlobalBindings(globals.globalTypeFunctionScope);
} }
}
static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size) static std::vector<TypeId> parseFormatString(NotNull<BuiltinTypes> builtinTypes, const char* data, size_t size)
{ {

View file

@ -143,10 +143,6 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
rci.traverse(ty); rci.traverse(ty);
// `UnpackConstraint` should not mutate `sourcePack`. // `UnpackConstraint` should not mutate `sourcePack`.
} }
else if (auto rpc = get<ReduceConstraint>(*this); FFlag::DebugLuauGreedyGeneralization && rpc)
{
rci.traverse(rpc->ty);
}
else if (auto rpc = get<ReducePackConstraint>(*this)) else if (auto rpc = get<ReducePackConstraint>(*this))
{ {
rci.traverse(rpc->tp); rci.traverse(rpc->tp);

View file

@ -33,25 +33,21 @@
LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr)
LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation) LUAU_FASTFLAGVARIABLE(LuauAvoidDoubleNegation)
LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
namespace Luau namespace Luau
{ {
@ -255,18 +251,15 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location; rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()}; module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
if (FFlag::LuauUserTypeFunTypecheck)
{
// Create module-local scope for the type function environment // Create module-local scope for the type function environment
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope); ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope);
localTypeFunctionScope->location = block->location; localTypeFunctionScope->location = block->location;
typeFunctionRuntime->rootScope = localTypeFunctionScope; typeFunctionRuntime->rootScope = localTypeFunctionScope;
}
rootScope->returnType = freshTypePack(scope, Polarity::Positive); rootScope->returnType = freshTypePack(scope, Polarity::Positive);
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, builtinTypes->anyTypePack, rootScope->returnType}); TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, builtinTypes->anyTypePack, rootScope->returnType});
@ -294,7 +287,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
} }
); );
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -313,7 +306,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
} }
); );
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -338,6 +331,12 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
LUAU_ASSERT(get<BlockedType>(ty)); LUAU_ASSERT(get<BlockedType>(ty));
asMutable(ty)->ty.emplace<BoundType>(domainTy); asMutable(ty)->ty.emplace<BoundType>(domainTy);
} }
if (FFlag::LuauSimplifyOutOfLine)
{
for (TypeId ty : unionsToSimplify)
addConstraint(scope, block->location, SimplifyConstraint{ty});
}
} }
void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block) void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block)
@ -345,13 +344,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
// We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre // Pre
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block); visitBlockWithoutChildScope(resumeScope, block);
// Post // Post
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -381,7 +380,7 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft); interiorFreeTypes.back().types.push_back(ft);
@ -403,7 +402,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
{ {
FreeTypePack f{scope.get(), polarity}; FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.back().typePacks.push_back(result); interiorFreeTypes.back().typePacks.push_back(result);
return result; return result;
} }
@ -829,7 +828,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
} }
else if (auto function = stat->as<AstStatTypeFunction>()) else if (auto function = stat->as<AstStatTypeFunction>())
{ {
if (FFlag::LuauUserTypeFunTypecheck)
hasTypeFunction = true; hasTypeFunction = true;
// If a type function w/ same name has already been defined, error for having duplicates // If a type function w/ same name has already been defined, error for having duplicates
@ -841,11 +839,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
continue; continue;
} }
// Variable becomes unused with the removal of FFlag::LuauUserTypeFunTypecheck
ScopePtr defnScope = FFlag::LuauUserTypeFunTypecheck ? nullptr : childScope(function, scope);
// Create TypeFunctionInstanceType // Create TypeFunctionInstanceType
std::vector<TypeId> typeParams; std::vector<TypeId> typeParams;
typeParams.reserve(function->body->args.size); typeParams.reserve(function->body->args.size);
@ -914,21 +908,18 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
} }
} }
if (FFlag::LuauUserTypeFunTypecheck && hasTypeFunction) if (hasTypeFunction)
typeFunctionEnvScope = std::make_shared<Scope>(typeFunctionRuntime->rootScope); typeFunctionEnvScope = std::make_shared<Scope>(typeFunctionRuntime->rootScope);
// Additional pass for user-defined type functions to fill in their environments completely // Additional pass for user-defined type functions to fill in their environments completely
for (AstStat* stat : block->body) for (AstStat* stat : block->body)
{ {
if (auto function = stat->as<AstStatTypeFunction>()) if (auto function = stat->as<AstStatTypeFunction>())
{
if (FFlag::LuauUserTypeFunTypecheck)
{ {
// Similar to global pre-population, create a binding for each type function in the scope upfront // Similar to global pre-population, create a binding for each type function in the scope upfront
TypeId bt = arena->addType(BlockedType{}); TypeId bt = arena->addType(BlockedType{});
typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location}; typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location};
astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope; astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope;
}
// Find the type function we have already created // Find the type function we have already created
TypeFunctionInstanceType* mainTypeFun = nullptr; TypeFunctionInstanceType* mainTypeFun = nullptr;
@ -948,8 +939,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData; UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
size_t level = 0; size_t level = 0;
if (FFlag::LuauUserTypeFunTypecheck)
{
auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level) auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level)
{ {
if (userFuncData.environment.find(name)) if (userFuncData.environment.find(name))
@ -962,8 +951,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition)) if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition))
{ {
if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false)) if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false))
scope->bindings[ty->userFuncData.definition->name] = scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location};
Binding{existing->typeId, ty->userFuncData.definition->location};
} }
} }
}; };
@ -979,32 +967,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
level++; level++;
} }
} }
else
{
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
for (auto& [name, tf] : curr->exportedTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
level++;
}
}
}
} }
} }
} }
@ -1289,6 +1251,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_)
visit(forScope, for_->body); visit(forScope, for_->body);
if (FFlag::LuauDfgAllowUpdatesInLoops)
scope->inheritAssignments(forScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -1350,6 +1315,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
visit(loopScope, forIn->body); visit(loopScope, forIn->body);
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
if (FFlag::LuauDfgAllowUpdatesInLoops)
scope->inheritAssignments(loopScope);
// This iter constraint must dispatch first. // This iter constraint must dispatch first.
forEachConstraint( forEachConstraint(
start, start,
@ -1373,6 +1341,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatWhile* whil
visit(whileScope, while_->body); visit(whileScope, while_->body);
if (FFlag::LuauDfgAllowUpdatesInLoops)
scope->inheritAssignments(whileScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -1384,6 +1355,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatRepeat* rep
check(repeatScope, repeat->condition); check(repeatScope, repeat->condition);
if (FFlag::LuauDfgAllowUpdatesInLoops)
scope->inheritAssignments(repeatScope);
return ControlFlow::None; return ControlFlow::None;
} }
@ -1483,8 +1457,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
DefId def = dfg->getDef(function->name); DefId def = dfg->getDef(function->name);
if (FFlag::LuauUngeneralizedTypesForRecursiveFunctions)
{
if (AstExprLocal* localName = function->name->as<AstExprLocal>()) if (AstExprLocal* localName = function->name->as<AstExprLocal>())
{ {
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
@ -1501,7 +1473,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
{ {
sig.bodyScope->rvalueRefinements[def] = sig.signature; sig.bodyScope->rvalueRefinements[def] = sig.signature;
} }
}
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
Checkpoint end = checkpoint(this); Checkpoint end = checkpoint(this);
@ -1781,17 +1752,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
} }
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function) ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function)
{
if (!FFlag::LuauUserTypeFunTypecheck)
return ControlFlow::None;
if (FFlag::LuauNoTypeFunctionsNamedTypeOf)
{ {
if (function->name == "typeof") if (function->name == "typeof")
{ {
reportError(function->location, ReservedIdentifier{"typeof"}); reportError(function->location, ReservedIdentifier{"typeof"});
} }
}
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
LUAU_ASSERT(scopePtr); LUAU_ASSERT(scopePtr);
@ -1802,7 +1767,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
// Place this function as a child of the non-type function scope // Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()}); scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -1820,7 +1785,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
} }
); );
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -1829,7 +1794,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio
sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -2229,7 +2194,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>())) else if (i < exprArgs.size() - 1 || !(arg->is<AstExprCall>() || arg->is<AstExprVarargs>()))
{ {
std::optional<TypeId> expectedType = std::nullopt; std::optional<TypeId> expectedType = std::nullopt;
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size()) if (i < expectedTypesForCall.size())
{ {
expectedType = expectedTypesForCall[i]; expectedType = expectedTypesForCall[i];
} }
@ -2240,7 +2205,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall*
else else
{ {
std::vector<std::optional<Luau::TypeId>> expectedTypes = {}; std::vector<std::optional<Luau::TypeId>> expectedTypes = {};
if (FFlag::LuauPropagateExpectedTypesForCalls && i < expectedTypesForCall.size()) if (i < expectedTypesForCall.size())
{ {
expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end()); expectedTypes.insert(expectedTypes.end(), expectedTypesForCall.begin() + int(i), expectedTypesForCall.end());
} }
@ -2423,7 +2388,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
// a[b] += c // a[b] += c
// //
// We only solve _one_ set of constraints for `b`. // We only solve _one_ set of constraints for `b`.
if (FFlag::LuauCacheInferencePerAstExpr && inferredExprCache.contains(expr)) if (inferredExprCache.contains(expr))
return inferredExprCache[expr]; return inferredExprCache[expr];
Inference result; Inference result;
@ -2478,7 +2443,6 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std::
result = Inference{freshType(scope)}; result = Inference{freshType(scope)};
} }
if (FFlag::LuauCacheInferencePerAstExpr)
inferredExprCache[expr] = result; inferredExprCache[expr] = result;
LUAU_ASSERT(result.ty); LUAU_ASSERT(result.ty);
@ -2494,7 +2458,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2521,7 +2485,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
return Inference{singletonType}; return Inference{singletonType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2682,7 +2646,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this); Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -2700,7 +2664,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
} }
); );
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks);
@ -2840,11 +2804,17 @@ Inference ConstraintGenerator::checkAstExprBinary(
} }
case AstExprBinary::Op::CompareLt: case AstExprBinary::Op::CompareLt:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGe: case AstExprBinary::Op::CompareGe:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance( TypeId resultType = createTypeFunctionInstance(
builtinTypeFunctions().ltFunc, builtinTypeFunctions().ltFunc,
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
@ -2856,11 +2826,17 @@ Inference ConstraintGenerator::checkAstExprBinary(
} }
case AstExprBinary::Op::CompareLe: case AstExprBinary::Op::CompareLe:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location);
return Inference{resultType, std::move(refinement)}; return Inference{resultType, std::move(refinement)};
} }
case AstExprBinary::Op::CompareGt: case AstExprBinary::Op::CompareGt:
{ {
if (FFlag::LuauNoMoreInjectiveTypeFunctions)
addConstraint(scope, location, EqualityConstraint{leftType, rightType});
TypeId resultType = createTypeFunctionInstance( TypeId resultType = createTypeFunctionInstance(
builtinTypeFunctions().leFunc, builtinTypeFunctions().leFunc,
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
@ -3192,7 +3168,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
ttv->definitionLocation = expr->location; ttv->definitionLocation = expr->location;
ttv->scope = scope.get(); ttv->scope = scope.get();
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
interiorFreeTypes.back().types.push_back(ty); interiorFreeTypes.back().types.push_back(ty);
else else
DEPRECATED_interiorTypes.back().push_back(ty); DEPRECATED_interiorTypes.back().push_back(ty);
@ -3245,6 +3221,35 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
{ {
LUAU_ASSERT(!indexValueLowerBound.empty()); LUAU_ASSERT(!indexValueLowerBound.empty());
if (FFlag::LuauSimplifyOutOfLine)
{
TypeId indexKey = nullptr;
TypeId indexValue = nullptr;
if (indexKeyLowerBound.size() == 1)
{
indexKey = *indexKeyLowerBound.begin();
}
else
{
indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
unionsToSimplify.push_back(indexKey);
}
if (indexValueLowerBound.size() == 1)
{
indexValue = *indexValueLowerBound.begin();
}
else
{
indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
unionsToSimplify.push_back(indexValue);
}
ttv->indexer = TableIndexer{indexKey, indexValue};
}
else
{
TypeId indexKey = indexKeyLowerBound.size() == 1 TypeId indexKey = indexKeyLowerBound.size() == 1
? *indexKeyLowerBound.begin() ? *indexKeyLowerBound.begin()
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
@ -3252,11 +3257,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
TypeId indexValue = indexValueLowerBound.size() == 1 TypeId indexValue = indexValueLowerBound.size() == 1
? *indexValueLowerBound.begin() ? *indexValueLowerBound.begin()
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
ttv->indexer = TableIndexer{indexKey, indexValue}; ttv->indexer = TableIndexer{indexKey, indexValue};
} }
}
if (expectedType) if (expectedType && !FFlag::LuauTableLiteralSubtypeSpecificCheck)
{ {
addConstraint( addConstraint(
scope, scope,
@ -3769,14 +3774,9 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
else if (ty->is<AstTypeOptional>()) else if (ty->is<AstTypeOptional>())
{ {
if (FFlag::LuauAlwaysResolveAstTypes)
result = builtinTypes->nilType; result = builtinTypes->nilType;
else
return builtinTypes->nilType;
} }
else if (auto unionAnnotation = ty->as<AstTypeUnion>()) else if (auto unionAnnotation = ty->as<AstTypeUnion>())
{
if (FFlag::LuauAlwaysResolveAstTypes)
{ {
if (unionAnnotation->types.size == 1) if (unionAnnotation->types.size == 1)
result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
@ -3791,22 +3791,7 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
result = arena->addType(UnionType{parts}); result = arena->addType(UnionType{parts});
} }
} }
else
{
if (unionAnnotation->types.size == 1)
return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
}
}
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>()) else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
{
if (FFlag::LuauAlwaysResolveAstTypes)
{ {
if (intersectionAnnotation->types.size == 1) if (intersectionAnnotation->types.size == 1)
result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
@ -3821,19 +3806,6 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
result = arena->addType(IntersectionType{parts}); result = arena->addType(IntersectionType{parts});
} }
} }
else
{
if (intersectionAnnotation->types.size == 1)
return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{
parts.push_back(resolveType(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
}
}
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>()) else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
{ {
result = resolveType_(scope, typeGroupAnnotation->type, inTypeArguments); result = resolveType_(scope, typeGroupAnnotation->type, inTypeArguments);
@ -4036,10 +4008,27 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
if (get<NeverType>(follow(rhs))) if (get<NeverType>(follow(rhs)))
return lhs; return lhs;
if (FFlag::LuauSimplifyOutOfLine)
{
TypeId result = simplifyUnion(scope, location, lhs, rhs);
if (is<UnionType>(follow(result)))
unionsToSimplify.push_back(result);
return result;
}
else
{
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location);
return resultType; return resultType;
} }
}
TypeId ConstraintGenerator::makeUnion(std::vector<TypeId> options)
{
LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine);
TypeId result = arena->addType(UnionType{std::move(options)});
unionsToSimplify.push_back(result);
return result;
}
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{ {
@ -4183,13 +4172,10 @@ void ConstraintGenerator::prepopulateGlobalScopeForFragmentTypecheck(const Scope
} }
if (FFlag::LuauUserTypeFunTypecheck)
{
// Handle type function globals as well, without preparing a module scope since they have a separate environment // Handle type function globals as well, without preparing a module scope since they have a separate environment
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp); program->visit(&tfgp);
} }
}
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
{ {
@ -4200,13 +4186,10 @@ void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, As
program->visit(&gp); program->visit(&gp);
if (FFlag::LuauUserTypeFunTypecheck)
{
// Handle type function globals as well, without preparing a module scope since they have a separate environment // Handle type function globals as well, without preparing a module scope since they have a separate environment
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg}; GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp); program->visit(&tfgp);
} }
}
bool ConstraintGenerator::recordPropertyAssignment(TypeId ty) bool ConstraintGenerator::recordPropertyAssignment(TypeId ty)
{ {
@ -4265,10 +4248,17 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As
scope->bindings[symbol] = Binding{tys.front(), location}; scope->bindings[symbol] = Binding{tys.front(), location};
else else
{ {
TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location); if (FFlag::LuauSimplifyOutOfLine)
{
TypeId ty = makeUnion(std::move(tys));
scope->bindings[symbol] = Binding{ty, location}; scope->bindings[symbol] = Binding{ty, location};
} }
else
{
TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location);
scope->bindings[symbol] = Binding{ty, location};
}
}
} }
} }
@ -4304,10 +4294,15 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
el = builtinTypes->neverType; el = builtinTypes->neverType;
else if (result.size() == 1) else if (result.size() == 1)
el = result[0]; el = result[0];
else
{
if (FFlag::LuauSimplifyOutOfLine)
el = makeUnion(std::move(result));
else else
el = module->internalTypes.addType(UnionType{std::move(result)}); el = module->internalTypes.addType(UnionType{std::move(result)});
} }
} }
}
}; };
for (const TypeId overload : funTys) for (const TypeId overload : funTys)

View file

@ -35,11 +35,14 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
namespace Luau namespace Luau
{ {
@ -686,6 +689,13 @@ void ConstraintSolver::initFreeTypeTracking()
block(dep, c); block(dep, c);
} }
} }
// Also check flag integrity while we're here
if (FFlag::DebugLuauGreedyGeneralization)
{
LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations);
LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions);
}
} }
void ConstraintSolver::generalizeOneType(TypeId ty) void ConstraintSolver::generalizeOneType(TypeId ty)
@ -729,7 +739,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity? ); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeType(constraint->scope, ty); trackInteriorFreeType(constraint->scope, ty);
return; return;
@ -819,6 +829,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*rpc, constraint, force); success = tryDispatch(*rpc, constraint, force);
else if (auto eqc = get<EqualityConstraint>(*constraint)) else if (auto eqc = get<EqualityConstraint>(*constraint))
success = tryDispatch(*eqc, constraint); success = tryDispatch(*eqc, constraint);
else if (auto sc = get<SimplifyConstraint>(*constraint))
success = tryDispatch(*sc, constraint);
else else
LUAU_ASSERT(false); LUAU_ASSERT(false);
@ -888,7 +900,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
{ {
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
ty = follow(ty); ty = follow(ty);
if (auto freeTy = get<FreeType>(ty)) if (auto freeTy = get<FreeType>(ty))
@ -910,7 +922,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
if (constraint->scope->interiorFreeTypePacks) if (constraint->scope->interiorFreeTypePacks)
{ {
@ -1519,7 +1531,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
for (TypeId freeTy : u2.newFreshTypes) for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy); trackInteriorFreeType(constraint->scope, freeTy);
@ -1540,6 +1552,51 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (c.result != result) if (c.result != result)
emplaceTypePack<BoundTypePack>(asMutable(c.result), result); emplaceTypePack<BoundTypePack>(asMutable(c.result), result);
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
{
FunctionType* inferredFuncTy = getMutable<FunctionType>(inferredTy);
LUAU_ASSERT(inferredFuncTy);
// Strip variadic anys from the argTypes of any functionType arguments
const auto [argsHead, argsTail] = flatten(inferredFuncTy->argTypes);
TypePack clippedArgs = {{}, argsTail};
bool clippedAny = false;
for (TypeId t : argsHead)
{
const FunctionType* f = get<FunctionType>(follow(t));
if (!f || !f->argTypes)
{
clippedArgs.head.push_back(t);
continue;
}
const TypePack* argTp = get<TypePack>(follow(f->argTypes));
if (!argTp || !argTp->tail)
{
clippedArgs.head.push_back(t);
continue;
}
if (const VariadicTypePack* argTpTail = get<VariadicTypePack>(follow(argTp->tail));
argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType)
{
const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head});
// Mint a new FunctionType in case the original came from another module
const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, f->retTypes});
FunctionType* newFunc = getMutable<FunctionType>(newFuncTypeId);
newFunc->argNames = f->argNames;
clippedArgs.head.push_back(newFuncTypeId);
clippedAny = true;
}
else
clippedArgs.head.push_back(t);
}
if (clippedAny)
inferredFuncTy->argTypes = arena->addTypePack(std::move(clippedArgs));
}
} }
for (const auto& [expanded, additions] : u2.expandedFreeTypes) for (const auto& [expanded, additions] : u2.expandedFreeTypes)
@ -1683,12 +1740,32 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy; (*c.astExpectedTypes)[expr] = expectedArgTy;
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
// Generic types are skipped over entirely, for now. // Generic types are skipped over entirely, for now.
if (containsGenerics.hasGeneric(expectedArgTy)) if (containsGenerics.hasGeneric(expectedArgTy))
{
if (!FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2 || !lambdaTy || !lambdaTy->argTypes)
continue; continue;
const TypePack* argTp = get<TypePack>(follow(lambdaTy->argTypes));
if (!argTp || !argTp->tail)
continue;
if (const VariadicTypePack* argTpTail = get<VariadicTypePack>(follow(argTp->tail));
argTpTail && argTpTail->hidden && argTpTail->ty == builtinTypes->anyType)
{
// Strip variadic any
const TypePackId anyLessArgTp = arena->addTypePack(TypePack{argTp->head});
const TypeId newFuncTypeId = arena->addType(FunctionType{anyLessArgTp, lambdaTy->retTypes});
FunctionType* newFunc = getMutable<FunctionType>(newFuncTypeId);
newFunc->argNames = lambdaTy->argNames;
(*c.astTypes)[expr] = newFuncTypeId;
}
continue;
}
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy); const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>(); const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
if (expectedLambdaTy && lambdaTy && lambdaExpr) if (expectedLambdaTy && lambdaTy && lambdaExpr)
@ -1899,7 +1976,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeType(constraint->scope, resultType); trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType}; tt->indexer = TableIndexer{indexType, resultType};
@ -1980,8 +2057,9 @@ bool ConstraintSolver::tryDispatchHasIndexer(
continue; continue;
r = follow(r); r = follow(r);
if (!get<ErrorType>(r)) if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r))
results.insert(r); results.insert(r);
} }
if (0 == results.size()) if (0 == results.size())
@ -2443,18 +2521,15 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks) for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location); unblock(r, constraint->location);
if (FFlag::LuauNewTypeFunReductionChecks2)
{
for (TypeId ity : result.irreducibleTypes) for (TypeId ity : result.irreducibleTypes)
uninhabitedTypeFunctions.insert(ity); uninhabitedTypeFunctions.insert(ity);
}
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty(); bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
ty = follow(ty); ty = follow(ty);
// If we couldn't reduce this type function, stick it in the set! // If we couldn't reduce this type function, stick it in the set!
if (get<TypeFunctionInstanceType>(ty) && (!FFlag::LuauNewTypeFunReductionChecks2 || !result.irreducibleTypes.find(ty))) if (get<TypeFunctionInstanceType>(ty) && !result.irreducibleTypes.find(ty))
typeFunctionsToFinalize[ty] = constraint; typeFunctionsToFinalize[ty] = constraint;
if (force || reductionFinished) if (force || reductionFinished)
@ -2531,6 +2606,83 @@ bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Co
return true; return true;
} }
struct FindAllUnionMembers : TypeOnceVisitor
{
DenseHashSet<TypeId> recordedTys{nullptr};
DenseHashSet<TypeId> blockedTys{nullptr};
FindAllUnionMembers()
: TypeOnceVisitor(/* skipBoundTypes */ true)
{
}
bool visit(TypeId ty) override
{
recordedTys.insert(ty);
return false;
}
bool visit(TypeId ty, const BlockedType&) override
{
blockedTys.insert(ty);
return false;
}
bool visit(TypeId ty, const PendingExpansionType&) override
{
blockedTys.insert(ty);
return false;
}
bool visit(TypeId ty, const FreeType&) override
{
blockedTys.insert(ty);
return false;
}
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
blockedTys.insert(ty);
return false;
}
bool visit(TypeId, const UnionType&) override
{
return true;
}
};
bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint)
{
TypeId target = follow(c.ty);
if (target->persistent || target->owningArena != arena || !is<UnionType>(target))
{
// If our target ends up being:
// - A persistent union like `false?`
// - A union from another arena
// - Something other than a union type
// Then it's either harmful or useless to fire this constraint, so we exit early.
return true;
}
FindAllUnionMembers finder;
finder.traverse(target);
if (!finder.blockedTys.empty())
{
for (TypeId ty : finder.blockedTys)
block(ty, constraint);
return false;
}
TypeId result = builtinTypes->neverType;
for (TypeId ty : finder.recordedTys)
{
ty = follow(ty);
if (ty == target)
continue;
result = simplifyUnion(constraint->scope, constraint->location, result, ty);
}
emplaceType<BoundType>(asMutable(target), result);
return true;
}
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force) bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{ {
iteratorTy = follow(iteratorTy); iteratorTy = follow(iteratorTy);

View file

@ -14,11 +14,13 @@
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset)
LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAGVARIABLE(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAGVARIABLE(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAGVARIABLE(LuauDfgMatchCGScopes)
LUAU_FASTFLAGVARIABLE(LuauDfgAllowUpdatesInLoops)
namespace Luau namespace Luau
{ {
@ -39,8 +41,6 @@ struct PushScope
} }
~PushScope() ~PushScope()
{
if (FFlag::LuauDfgScopeStackTrueReset)
{ {
// If somehow this stack has _shrunk_ to be smaller than we expect, // If somehow this stack has _shrunk_ to be smaller than we expect,
// something very strange has happened. // something very strange has happened.
@ -48,11 +48,6 @@ struct PushScope
while (stack.size() > previousSize) while (stack.size() > previousSize)
stack.pop_back(); stack.pop_back();
} }
else
{
stack.pop_back();
}
}
}; };
const RefinementKey* RefinementKeyArena::leaf(DefId def) const RefinementKey* RefinementKeyArena::leaf(DefId def)
@ -157,6 +152,9 @@ void DfgScope::inherit(const DfgScope* childScope)
bool DfgScope::canUpdateDefinition(Symbol symbol) const bool DfgScope::canUpdateDefinition(Symbol symbol) const
{ {
if (FFlag::LuauDfgAllowUpdatesInLoops)
return true;
for (const DfgScope* current = this; current; current = current->parent) for (const DfgScope* current = this; current; current = current->parent)
{ {
if (current->bindings.find(symbol)) if (current->bindings.find(symbol))
@ -170,6 +168,9 @@ bool DfgScope::canUpdateDefinition(Symbol symbol) const
bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const bool DfgScope::canUpdateDefinition(DefId def, const std::string& key) const
{ {
if (FFlag::LuauDfgAllowUpdatesInLoops)
return true;
for (const DfgScope* current = this; current; current = current->parent) for (const DfgScope* current = this; current; current = current->parent)
{ {
if (auto props = current->props.find(def)) if (auto props = current->props.find(def))
@ -533,9 +534,40 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatIf* i)
ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w) ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
{ {
// TODO(controlflow): entry point has a back edge from exit point // FIXME: This is unsound, as it does not consider the _second_ loop
// iteration. Consider something like:
//
// local function f(_: number) end
// local x = 42
// while math.random () > 0.5 do
// f(x)
// x = ""
// end
//
// While the first iteration is fine, the second iteration would
// allow a string to flow into a position that expects.
DfgScope* whileScope = makeChildScope(DfgScope::Loop); DfgScope* whileScope = makeChildScope(DfgScope::Loop);
if (FFlag::LuauDfgAllowUpdatesInLoops)
{
ControlFlow cf;
{
PushScope ps{scopeStack, whileScope};
visitExpr(w->condition);
cf = visit(w->body);
}
auto scope = currentScope();
// If the inner loop unconditioanlly returns or throws we shouldn't
// consume any type state from the loop body.
if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws))
join(scope, scope, whileScope);
return ControlFlow::None;
}
else
{
{ {
PushScope ps{scopeStack, whileScope}; PushScope ps{scopeStack, whileScope};
visitExpr(w->condition); visitExpr(w->condition);
@ -549,12 +581,36 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatWhile* w)
return ControlFlow::None; return ControlFlow::None;
} }
}
ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r) ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
{ {
// TODO(controlflow): entry point has a back edge from exit point // See comment in visit(AstStatWhile*): this is unsound as it
// does not consider the _second_ loop iteration.
DfgScope* repeatScope = makeChildScope(DfgScope::Loop); DfgScope* repeatScope = makeChildScope(DfgScope::Loop);
if (FFlag::LuauDfgAllowUpdatesInLoops)
{
ControlFlow cf;
{
PushScope ps{scopeStack, repeatScope};
cf = visitBlockWithoutChildScope(r->body);
visitExpr(r->condition);
}
// Ultimately: the options for a repeat-until loop are more
// straightforward.
currentScope()->inherit(repeatScope);
// `repeat` loops will unconditionally fire: if the internal control
// flow is unconditionally a break or continue, then we have linear
// control flow, but if it's throws or returns, then we need to
// return _that_ to the parent.
return matches(cf, ControlFlow::Breaks | ControlFlow::Continues) ? ControlFlow::None : cf;
}
else
{
{ {
PushScope ps{scopeStack, repeatScope}; PushScope ps{scopeStack, repeatScope};
visitBlockWithoutChildScope(r->body); visitBlockWithoutChildScope(r->body);
@ -568,6 +624,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatRepeat* r)
return ControlFlow::None; return ControlFlow::None;
} }
}
ControlFlow DataFlowGraphBuilder::visit(AstStatBreak* b) ControlFlow DataFlowGraphBuilder::visit(AstStatBreak* b)
{ {
@ -635,6 +692,8 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l)
ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f) ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
{ {
// See comment in visit(AstStatWhile*): this is unsound as it
// does not consider the _second_ loop iteration.
DfgScope* forScope = makeChildScope(DfgScope::Loop); DfgScope* forScope = makeChildScope(DfgScope::Loop);
visitExpr(f->from); visitExpr(f->from);
@ -642,6 +701,34 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (f->step) if (f->step)
visitExpr(f->step); visitExpr(f->step);
if (FFlag::LuauDfgAllowUpdatesInLoops)
{
ControlFlow cf;
{
PushScope ps{scopeStack, forScope};
if (f->var->annotation)
visitType(f->var->annotation);
DefId def = defArena->freshCell(f->var, f->var->location);
graph.localDefs[f->var] = def;
currentScope()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def);
cf = visit(f->body);
}
auto scope = currentScope();
// If the inner loop unconditioanlly returns or throws we shouldn't
// consume any type state from the loop body.
if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws))
join(scope, scope, forScope);
return ControlFlow::None;
}
else
{
{ {
PushScope ps{scopeStack, forScope}; PushScope ps{scopeStack, forScope};
@ -667,11 +754,51 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
return ControlFlow::None; return ControlFlow::None;
} }
}
ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f) ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
{ {
DfgScope* forScope = makeChildScope(DfgScope::Loop); DfgScope* forScope = makeChildScope(DfgScope::Loop);
if (FFlag::LuauDfgAllowUpdatesInLoops)
{
ControlFlow cf;
{
PushScope ps{scopeStack, forScope};
for (AstLocal* local : f->vars)
{
if (local->annotation)
visitType(local->annotation);
DefId def = defArena->freshCell(local, local->location);
graph.localDefs[local] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[local] = def;
else
currentScope_DEPRECATED()->bindings[local] = def;
captures[local].allVersions.push_back(def);
}
// TODO(controlflow): entry point has a back edge from exit point
// We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`)
for (AstExpr* e : f->values)
visitExpr(e);
cf = visit(f->body);
}
auto scope = currentScope();
// If the inner loop unconditioanlly returns or throws we shouldn't
// consume any type state from the loop body.
if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws))
join(scope, scope, forScope);
return ControlFlow::None;
}
else
{
{ {
PushScope ps{scopeStack, forScope}; PushScope ps{scopeStack, forScope};
@ -703,6 +830,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatForIn* f)
return ControlFlow::None; return ControlFlow::None;
} }
}
ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a) ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
{ {
@ -1112,10 +1240,19 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
{ {
visitExpr(b->left); visitExpr(b->left);
if (FFlag::LuauDfgMatchCGScopes)
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(b->right); visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr}; return {defArena->freshCell(Symbol{}, b->location), nullptr};
} }
else
{
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
}
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
{ {
@ -1126,10 +1263,30 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
{
if (FFlag::LuauDfgMatchCGScopes)
{
// In the constraint generator, the condition, consequence, and
// alternative all have distinct scopes.
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->condition);
}
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->trueExpr);
}
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(i->falseExpr);
}
}
else
{ {
visitExpr(i->condition); visitExpr(i->condition);
visitExpr(i->trueExpr); visitExpr(i->trueExpr);
visitExpr(i->falseExpr); visitExpr(i->falseExpr);
}
return {defArena->freshCell(Symbol{}, i->location), nullptr}; return {defArena->freshCell(Symbol{}, i->location), nullptr};
} }

View file

@ -7,7 +7,7 @@ LUAU_FASTFLAG(LuauTypeFunOptional)
namespace Luau namespace Luau
{ {
static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC(
@checked declare function require(target: any): any @checked declare function require(target: any): any
@ -60,7 +60,7 @@ declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
declare bit32: { declare bit32: {
band: @checked (...number) -> number, band: @checked (...number) -> number,
@ -82,7 +82,7 @@ declare bit32: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
declare math: { declare math: {
frexp: @checked (n: number) -> (number, number), frexp: @checked (n: number) -> (number, number),
@ -133,7 +133,7 @@ declare math: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionOsSrc = R"BUILTIN_SRC(
type DateTypeArg = { type DateTypeArg = {
year: number, year: number,
@ -166,7 +166,7 @@ declare os: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
declare coroutine: { declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread, create: <A..., R...>(f: (A...) -> R...) -> thread,
@ -181,7 +181,7 @@ declare coroutine: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
declare table: { declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string, concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
@ -207,7 +207,7 @@ declare table: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
declare debug: { declare debug: {
info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & (<A..., R1...>(func: (A...) -> R1..., options: string) -> ...any), info: ((thread: thread, level: number, options: string) -> ...any) & ((level: number, options: string) -> ...any) & (<A..., R1...>(func: (A...) -> R1..., options: string) -> ...any),
@ -216,7 +216,7 @@ declare debug: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: { declare utf8: {
char: @checked (...number) -> string, char: @checked (...number) -> string,
@ -229,7 +229,7 @@ declare utf8: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API --- Buffer API
declare buffer: { declare buffer: {
create: @checked (size: number) -> buffer, create: @checked (size: number) -> buffer,
@ -262,7 +262,7 @@ declare buffer: {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) static const char* const kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? R"BUILTIN_SRC( ? R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties -- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties
@ -340,7 +340,7 @@ std::string getBuiltinDefinitionSource()
} }
// TODO: split into separate tagged unions when the new solver can appropriately handle that. // TODO: split into separate tagged unions when the new solver can appropriately handle that.
static const std::string kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionTypeMethodSrc = R"BUILTIN_SRC(
export type type = { export type type = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" | tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
@ -393,7 +393,7 @@ export type type = {
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: { declare types: {
unknown: type, unknown: type,
@ -416,7 +416,7 @@ declare types: {
} }
)BUILTIN_SRC"; )BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC( static constexpr const char* kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: { declare types: {
unknown: type, unknown: type,

View file

@ -839,6 +839,11 @@ struct ErrorConverter
return result; return result;
} }
std::string operator()(const UnexpectedArrayLikeTableItem&) const
{
return "Unexpected array-like table item: the indexer key type of this table is not `number`.";
}
}; };
struct InvalidNameChecker struct InvalidNameChecker
@ -1432,6 +1437,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause) for (auto& ty : e.cause)
ty = clone(ty); ty = clone(ty);
} }
else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>)
{
}
else if constexpr (std::is_same_v<T, ReservedIdentifier>) else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{ {
} }

View file

@ -33,8 +33,6 @@ LUAU_FASTFLAGVARIABLE(LuauBetterCursorInCommentDetection)
LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes) LUAU_FASTFLAGVARIABLE(LuauAllFreeTypesHaveScopes)
LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes) LUAU_FASTFLAGVARIABLE(LuauPersistConstraintGenerationScopes)
LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings) LUAU_FASTFLAGVARIABLE(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauFragmentNoTypeFunEval)
LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection)
LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
@ -833,7 +831,7 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse_DEPRECATED(AstSt
} }
} }
} }
else if (auto typeFun = stat->as<AstStatTypeFunction>(); typeFun && FFlag::LuauUserTypeFunTypecheck) else if (auto typeFun = stat->as<AstStatTypeFunction>())
{ {
if (typeFun->location.contains(cursorPos)) if (typeFun->location.contains(cursorPos))
{ {
@ -1107,7 +1105,6 @@ FragmentTypeCheckResult typecheckFragment_(
/// User defined type functions runtime /// User defined type functions runtime
TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits}); TypeFunctionRuntime typeFunctionRuntime(iceHandler, NotNull{&limits});
if (FFlag::LuauFragmentNoTypeFunEval || FFlag::LuauUserTypeFunTypecheck)
typeFunctionRuntime.allowEvaluation = false; typeFunctionRuntime.allowEvaluation = false;
/// Create a DataFlowGraph just for the surrounding context /// Create a DataFlowGraph just for the surrounding context
@ -1141,13 +1138,10 @@ FragmentTypeCheckResult typecheckFragment_(
freshChildOfNearestScope->interiorFreeTypePacks.emplace(); freshChildOfNearestScope->interiorFreeTypePacks.emplace();
cg.rootScope = freshChildOfNearestScope.get(); cg.rootScope = freshChildOfNearestScope.get();
if (FFlag::LuauUserTypeFunTypecheck)
{
// Create module-local scope for the type function environment // Create module-local scope for the type function environment
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope); ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope);
localTypeFunctionScope->location = root->location; localTypeFunctionScope->location = root->location;
cg.typeFunctionRuntime->rootScope = localTypeFunctionScope; cg.typeFunctionRuntime->rootScope = localTypeFunctionScope;
}
reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart); reportWaypoint(reporter, FragmentAutocompleteWaypoint::CloneAndSquashScopeStart);
cloneTypesFromFragment( cloneTypesFromFragment(

View file

@ -39,7 +39,6 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
@ -47,8 +46,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
namespace Luau namespace Luau
{ {
@ -1098,8 +1095,6 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
BuildQueueItem& item = state->buildQueueItems[itemPos]; BuildQueueItem& item = state->buildQueueItems[itemPos];
if (DFFlag::LuauRethrowKnownExceptions)
{
try try
{ {
checkBuildQueueItem(item); checkBuildQueueItem(item);
@ -1108,18 +1103,6 @@ void Frontend::performQueueItemTask(std::shared_ptr<BuildQueueWorkState> state,
{ {
item.exception = std::current_exception(); item.exception = std::current_exception();
} }
}
else
{
try
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
}
{ {
std::unique_lock guard(state->mtx); std::unique_lock guard(state->mtx);
@ -1402,7 +1385,7 @@ ModulePtr check(
SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes);
TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}};
typeFunctionRuntime.allowEvaluation = FFlag::LuauTypeFunResultInAutocomplete || sourceModule.parseErrors.empty(); typeFunctionRuntime.allowEvaluation = true;
ConstraintGenerator cg{ ConstraintGenerator cg{
result, result,

View file

@ -16,7 +16,7 @@
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization3)
namespace Luau namespace Luau
{ {
@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
return true; return true;
@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
unsealedTables.insert(ty); unsealedTables.insert(ty);
else else
{ {
@ -559,7 +559,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
// {[K]: V} is equivalent to three functions: get, set, and iterate // {[K]: V} is equivalent to three functions: get, set, and iterate
// //
@ -617,7 +617,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
GeneralizationParams<TypePackId>& params = typePacks[tp]; GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount; ++params.useCount;
@ -1159,7 +1159,7 @@ struct RemoveType : Substitution // NOLINT
for (TypeId ty : ut) for (TypeId ty : ut)
{ {
if (ty != needle) if (ty != needle && !is<NeverType>(ty))
newParts.insert(ty); newParts.insert(ty);
} }
@ -1180,7 +1180,7 @@ struct RemoveType : Substitution // NOLINT
for (TypeId ty : it) for (TypeId ty : it)
{ {
if (ty != needle) if (ty != needle && !is<UnknownType>(ty))
newParts.insert(ty); newParts.insert(ty);
} }
@ -1301,7 +1301,23 @@ GeneralizationResult<TypeId> generalizeType(
if (follow(ub) != freeTy) if (follow(ub) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), ub); emplaceType<BoundType>(asMutable(freeTy), ub);
else if (!isWithinFunction || params.useCount == 1) else if (!isWithinFunction || params.useCount == 1)
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); {
// If we have some free type:
//
// A <: 'b < C
//
// We can approximately generalize this to the intersection of it's
// bounds, taking care to avoid constructing a degenerate
// union or intersection by clipping the free type from the upper
// and lower bounds, then also cleaning the resulting intersection.
std::optional<TypeId> removedLb = removeType(arena, builtinTypes, ft->lowerBound, freeTy);
if (!removedLb)
return {std::nullopt, false, true};
std::optional<TypeId> cleanedTy = removeType(arena, builtinTypes, arena->addType(IntersectionType{{*removedLb, ub}}), freeTy);
if (!cleanedTy)
return {std::nullopt, false, true};
emplaceType<BoundType>(asMutable(freeTy), *cleanedTy);
}
else else
{ {
// if the upper bound is the type in question, we don't actually have an upper bound. // if the upper bound is the type in question, we don't actually have an upper bound.
@ -1374,7 +1390,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
FunctionType* functionTy = getMutable<FunctionType>(ty); FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t) auto pushGeneric = [&](TypeId t)

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
namespace Luau namespace Luau
{ {
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID> template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty) static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{ {
if (!FFlag::LuauNonReentrantGeneralization2) if (!FFlag::LuauNonReentrantGeneralization3)
return; return;
InferPolarity infer{arena, scope}; InferPolarity infer{arena, scope};

View file

@ -248,6 +248,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << " } } "; stream << " } } ";
} }
else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>)
stream << "UnexpectedArrayLikeTableItem {}";
else else
static_assert(always_false_v<T>, "Non-exhaustive type switch"); static_assert(always_false_v<T>, "Non-exhaustive type switch");
} }

View file

@ -3240,7 +3240,7 @@ static void fillBuiltinGlobals(LintContext& context, const AstNameTable& names,
} }
} }
static const char* fuzzyMatch(std::string_view str, const char** array, size_t size) static const char* fuzzyMatch(std::string_view str, const char* const* array, size_t size)
{ {
if (FInt::LuauSuggestionDistance == 0) if (FInt::LuauSuggestionDistance == 0)
return nullptr; return nullptr;

View file

@ -22,8 +22,6 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
@ -367,28 +365,18 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstStatWhile* whileStatement) NonStrictContext visit(AstStatWhile* whileStatement)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue); NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue);
NonStrictContext body = visit(whileStatement->body); NonStrictContext body = visit(whileStatement->body);
return NonStrictContext::disjunction(builtinTypes, arena, condition, body); return NonStrictContext::disjunction(builtinTypes, arena, condition, body);
} }
else
return {};
}
NonStrictContext visit(AstStatRepeat* repeatStatement) NonStrictContext visit(AstStatRepeat* repeatStatement)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
NonStrictContext body = visit(repeatStatement->body); NonStrictContext body = visit(repeatStatement->body);
NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue); NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, body, condition); return NonStrictContext::disjunction(builtinTypes, arena, body, condition);
} }
else
return {};
}
NonStrictContext visit(AstStatBreak* breakStatement) NonStrictContext visit(AstStatBreak* breakStatement)
{ {
@ -401,14 +389,11 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstStatReturn* returnStatement) NonStrictContext visit(AstStatReturn* returnStatement)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
// TODO: this is believing existing code, but i'm not sure if this makes sense // TODO: this is believing existing code, but i'm not sure if this makes sense
// for how the contexts are handled // for how the contexts are handled
for (AstExpr* expr : returnStatement->list) for (AstExpr* expr : returnStatement->list)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
}
return {}; return {};
} }
@ -430,8 +415,6 @@ struct NonStrictTypeChecker
if (FFlag::LuauNewNonStrictVisitTypes2) if (FFlag::LuauNewNonStrictVisitTypes2)
visit(forStatement->var->annotation); visit(forStatement->var->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
{
// TODO: throwing out context based on same principle as existing code? // TODO: throwing out context based on same principle as existing code?
if (forStatement->from) if (forStatement->from)
visit(forStatement->from, ValueContext::RValue); visit(forStatement->from, ValueContext::RValue);
@ -441,11 +424,6 @@ struct NonStrictTypeChecker
visit(forStatement->step, ValueContext::RValue); visit(forStatement->step, ValueContext::RValue);
return visit(forStatement->body); return visit(forStatement->body);
} }
else
{
return {};
}
}
NonStrictContext visit(AstStatForIn* forInStatement) NonStrictContext visit(AstStatForIn* forInStatement)
{ {
@ -455,38 +433,25 @@ struct NonStrictTypeChecker
visit(var->annotation); visit(var->annotation);
} }
if (FFlag::LuauNonStrictVisitorImprovements)
{
for (AstExpr* rhs : forInStatement->values) for (AstExpr* rhs : forInStatement->values)
visit(rhs, ValueContext::RValue); visit(rhs, ValueContext::RValue);
return visit(forInStatement->body); return visit(forInStatement->body);
} }
else
{
return {};
}
}
NonStrictContext visit(AstStatAssign* assign) NonStrictContext visit(AstStatAssign* assign)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstExpr* lhs : assign->vars) for (AstExpr* lhs : assign->vars)
visit(lhs, ValueContext::LValue); visit(lhs, ValueContext::LValue);
for (AstExpr* rhs : assign->values) for (AstExpr* rhs : assign->values)
visit(rhs, ValueContext::RValue); visit(rhs, ValueContext::RValue);
}
return {}; return {};
} }
NonStrictContext visit(AstStatCompoundAssign* compoundAssign) NonStrictContext visit(AstStatCompoundAssign* compoundAssign)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
visit(compoundAssign->var, ValueContext::LValue); visit(compoundAssign->var, ValueContext::LValue);
visit(compoundAssign->value, ValueContext::RValue); visit(compoundAssign->value, ValueContext::RValue);
}
return {}; return {};
} }
@ -555,14 +520,11 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstStatError* error) NonStrictContext visit(AstStatError* error)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstStat* stat : error->statements) for (AstStat* stat : error->statements)
visit(stat); visit(stat);
for (AstExpr* expr : error->expressions) for (AstExpr* expr : error->expressions)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
}
return {}; return {};
} }
@ -617,10 +579,7 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprGroup* group, ValueContext context) NonStrictContext visit(AstExprGroup* group, ValueContext context)
{ {
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(group->expr, context); return visit(group->expr, context);
else
return {};
} }
NonStrictContext visit(AstExprConstantNil* expr) NonStrictContext visit(AstExprConstantNil* expr)
@ -649,8 +608,6 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstExprGlobal* global, ValueContext context) NonStrictContext visit(AstExprGlobal* global, ValueContext context)
{
if (FFlag::LuauNewNonStrictWarnOnUnknownGlobals)
{ {
// We don't file unknown symbols for LValues. // We don't file unknown symbols for LValues.
if (context == ValueContext::LValue) if (context == ValueContext::LValue)
@ -661,7 +618,6 @@ struct NonStrictTypeChecker
{ {
reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location); reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location);
} }
}
return {}; return {};
} }
@ -783,23 +739,16 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprIndexName* indexName, ValueContext context) NonStrictContext visit(AstExprIndexName* indexName, ValueContext context)
{ {
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(indexName->expr, context); return visit(indexName->expr, context);
else
return {};
} }
NonStrictContext visit(AstExprIndexExpr* indexExpr, ValueContext context) NonStrictContext visit(AstExprIndexExpr* indexExpr, ValueContext context)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
NonStrictContext expr = visit(indexExpr->expr, context); NonStrictContext expr = visit(indexExpr->expr, context);
NonStrictContext index = visit(indexExpr->index, ValueContext::RValue); NonStrictContext index = visit(indexExpr->index, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, expr, index); return NonStrictContext::disjunction(builtinTypes, arena, expr, index);
} }
else
return {};
}
NonStrictContext visit(AstExprFunction* exprFn) NonStrictContext visit(AstExprFunction* exprFn)
{ {
@ -839,8 +788,6 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstExprTable* table) NonStrictContext visit(AstExprTable* table)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (auto [_, key, value] : table->items) for (auto [_, key, value] : table->items)
{ {
@ -848,40 +795,28 @@ struct NonStrictTypeChecker
visit(key, ValueContext::RValue); visit(key, ValueContext::RValue);
visit(value, ValueContext::RValue); visit(value, ValueContext::RValue);
} }
}
return {}; return {};
} }
NonStrictContext visit(AstExprUnary* unary) NonStrictContext visit(AstExprUnary* unary)
{ {
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(unary->expr, ValueContext::RValue); return visit(unary->expr, ValueContext::RValue);
else
return {};
} }
NonStrictContext visit(AstExprBinary* binary) NonStrictContext visit(AstExprBinary* binary)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
NonStrictContext lhs = visit(binary->left, ValueContext::RValue); NonStrictContext lhs = visit(binary->left, ValueContext::RValue);
NonStrictContext rhs = visit(binary->right, ValueContext::RValue); NonStrictContext rhs = visit(binary->right, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs); return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs);
} }
else
return {};
}
NonStrictContext visit(AstExprTypeAssertion* typeAssertion) NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
{ {
if (FFlag::LuauNewNonStrictVisitTypes2) if (FFlag::LuauNewNonStrictVisitTypes2)
visit(typeAssertion->annotation); visit(typeAssertion->annotation);
if (FFlag::LuauNonStrictVisitorImprovements)
return visit(typeAssertion->expr, ValueContext::RValue); return visit(typeAssertion->expr, ValueContext::RValue);
else
return {};
} }
NonStrictContext visit(AstExprIfElse* ifElse) NonStrictContext visit(AstExprIfElse* ifElse)
@ -893,23 +828,17 @@ struct NonStrictTypeChecker
} }
NonStrictContext visit(AstExprInterpString* interpString) NonStrictContext visit(AstExprInterpString* interpString)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstExpr* expr : interpString->expressions) for (AstExpr* expr : interpString->expressions)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
}
return {}; return {};
} }
NonStrictContext visit(AstExprError* error) NonStrictContext visit(AstExprError* error)
{
if (FFlag::LuauNonStrictVisitorImprovements)
{ {
for (AstExpr* expr : error->expressions) for (AstExpr* expr : error->expressions)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
}
return {}; return {};
} }

View file

@ -21,8 +21,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauFixInfiniteRecursionInNormalization)
LUAU_FASTFLAGVARIABLE(LuauNormalizationCatchMetatableCycles)
namespace Luau namespace Luau
{ {
@ -2605,8 +2603,6 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
if (hprop.readTy.has_value()) if (hprop.readTy.has_value())
{ {
if (tprop.readTy.has_value()) if (tprop.readTy.has_value())
{
if (FFlag::LuauFixInfiniteRecursionInNormalization)
{ {
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result; TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
@ -2626,42 +2622,6 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
thereSubHere &= (ty == tprop.readTy); thereSubHere &= (ty == tprop.readTy);
} }
else else
{
// if the intersection of the read types of a property is uninhabited, the whole table is `never`.
// We've seen these table prop elements before and we're about to ask if their intersection
// is inhabited
auto pair1 = std::pair{*hprop.readTy, *tprop.readTy};
auto pair2 = std::pair{*tprop.readTy, *hprop.readTy};
if (seenTablePropPairs.contains(pair1) || seenTablePropPairs.contains(pair2))
{
seenTablePropPairs.erase(pair1);
seenTablePropPairs.erase(pair2);
return {builtinTypes->neverType};
}
else
{
seenTablePropPairs.insert(pair1);
seenTablePropPairs.insert(pair2);
}
// FIXME(ariel): this is being added in a flag removal, so not changing the semantics here, but worth noting that this
// fresh `seenSet` is definitely a bug. we already have `seenSet` from the parameter that _should_ have been used here.
Set<TypeId> seenSet{nullptr};
NormalizationResult res = isIntersectionInhabited(*hprop.readTy, *tprop.readTy, seenTablePropPairs, seenSet);
seenTablePropPairs.erase(pair1);
seenTablePropPairs.erase(pair2);
if (NormalizationResult::True != res)
return {builtinTypes->neverType};
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy);
}
}
else
{ {
prop.readTy = *hprop.readTy; prop.readTy = *hprop.readTy;
thereSubHere = false; thereSubHere = false;
@ -3352,21 +3312,6 @@ NormalizationResult Normalizer::intersectNormalWithTy(
return NormalizationResult::True; return NormalizationResult::True;
} }
void makeTableShared_DEPRECATED(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)
prop.makeShared();
}
else if (auto metatableTy = get<MetatableType>(ty))
{
makeTableShared_DEPRECATED(metatableTy->metatable);
makeTableShared_DEPRECATED(metatableTy->table);
}
}
void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen) void makeTableShared(TypeId ty, DenseHashSet<TypeId>& seen)
{ {
ty = follow(ty); ty = follow(ty);
@ -3490,10 +3435,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm)
result.reserve(result.size() + norm.tables.size()); result.reserve(result.size() + norm.tables.size());
for (auto table : norm.tables) for (auto table : norm.tables)
{ {
if (FFlag::LuauNormalizationCatchMetatableCycles)
makeTableShared(table); makeTableShared(table);
else
makeTableShared_DEPRECATED(table);
result.push_back(table); result.push_back(table);
} }
} }

View file

@ -11,6 +11,7 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
namespace Luau namespace Luau
{ {
@ -287,6 +288,25 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
return {Analysis::Ok, {}}; return {Analysis::Ok, {}};
} }
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
{
if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && reason.superPath == justArguments)
{
// We have an arity mismatch if the argument tail is a generic type pack
if (auto fnArgs = get<TypePack>(fn->argTypes))
{
// TODO: Determine whether arguments have incorrect type, incorrect count, or both (CLI-152070)
if (get<GenericTypePack>(fnArgs->tail))
{
auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes);
TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg}};
return {Analysis::ArityMismatch, {error}};
}
}
}
}
} }
ErrorVec errors; ErrorVec errors;

View file

@ -10,7 +10,6 @@
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256)
LUAU_FASTFLAG(LuauSyntheticErrors)
namespace Luau namespace Luau
{ {
@ -56,8 +55,6 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
return ty; return ty;
} }
else if constexpr (std::is_same_v<T, ErrorType>) else if constexpr (std::is_same_v<T, ErrorType>)
{
if (FFlag::LuauSyntheticErrors)
{ {
LUAU_ASSERT(ty->persistent || a.synthetic); LUAU_ASSERT(ty->persistent || a.synthetic);
@ -71,12 +68,6 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
clone.synthetic = a.synthetic; clone.synthetic = a.synthetic;
return dest.addType(clone); return dest.addType(clone);
} }
else
{
LUAU_ASSERT(ty->persistent);
return ty;
}
}
else if constexpr (std::is_same_v<T, UnknownType>) else if constexpr (std::is_same_v<T, UnknownType>)
{ {
LUAU_ASSERT(ty->persistent); LUAU_ASSERT(ty->persistent);

View file

@ -17,12 +17,10 @@
#include "Luau/TypePath.h" #include "Luau/TypePath.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
namespace Luau namespace Luau
{ {
@ -101,7 +99,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result; return result;
} }
@ -120,7 +118,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
result.insert(r); result.insert(r);
} }
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit)) if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result; return result;
} }
@ -815,7 +813,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X>(X) -> () <: (T) -> () // <X>(X) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize); std::vector<TypeId> headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2
? std::vector<TypeId>(begin(superHead) + headSize, end(superHead))
: std::vector<TypeId>(begin(superHead), begin(superHead) + headSize);
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
if (TypePackId* other = env.getMappedPackBounds(*subTail)) if (TypePackId* other = env.getMappedPackBounds(*subTail))
@ -870,11 +870,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X...>(X...) -> () <: (T) -> () // <X...>(X...) -> () <: (T) -> ()
// Possible optimization: If headSize == 0 then we can just use subTp as-is. // Possible optimization: If headSize == 0 then we can just use subTp as-is.
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize); std::vector<TypeId> headSlice = FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2
? std::vector<TypeId>(begin(subHead) + headSize, end(subHead))
: std::vector<TypeId>(begin(subHead), begin(subHead) + headSize);
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
if (TypePackId* other = env.getMappedPackBounds(*superTail)) if (TypePackId* other = env.getMappedPackBounds(*superTail))
// TODO: TypePath can't express "slice of a pack + its tail". // TODO: TypePath can't express "slice of a pack + its tail".
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
else
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
else else
env.mappedGenericPacks.try_insert(*superTail, subTailPack); env.mappedGenericPacks.try_insert(*superTail, subTailPack);

View file

@ -14,17 +14,11 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau namespace Luau
{ {
// A fast approximation of subTy <: superTy
static bool fastIsSubtype(TypeId subTy, TypeId superTy)
{
Relation r = relate(superTy, subTy);
return r == Relation::Coincident || r == Relation::Superset;
}
static bool isRecord(const AstExprTable::Item& item) static bool isRecord(const AstExprTable::Item& item)
{ {
if (item.kind == AstExprTable::Item::Record) if (item.kind == AstExprTable::Item::Record)
@ -35,80 +29,6 @@ static bool isRecord(const AstExprTable::Item& item)
return false; return false;
} }
static std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, TypeId exprType, NotNull<BuiltinTypes> builtinTypes)
{
if (tables.empty())
return std::nullopt;
const TableType* exprTable = get<TableType>(follow(exprType));
if (!exprTable)
return std::nullopt;
size_t tableCount = 0;
std::optional<TypeId> firstTable;
for (TypeId ty : tables)
{
ty = follow(ty);
if (auto tt = get<TableType>(ty))
{
// If the expected table has a key whose type is a string or boolean
// singleton and the corresponding exprType property does not match,
// then skip this table.
if (!firstTable)
firstTable = ty;
++tableCount;
for (const auto& [name, expectedProp] : tt->props)
{
if (!expectedProp.readTy)
continue;
const TypeId expectedType = follow(*expectedProp.readTy);
auto st = get<SingletonType>(expectedType);
if (!st)
continue;
auto it = exprTable->props.find(name);
if (it == exprTable->props.end())
continue;
const auto& [_name, exprProp] = *it;
if (!exprProp.readTy)
continue;
const TypeId propType = follow(*exprProp.readTy);
const FreeType* ft = get<FreeType>(propType);
if (ft && get<SingletonType>(ft->lowerBound))
{
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType))
{
return ty;
}
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound))
{
return ty;
}
}
}
}
}
if (tableCount == 1)
{
LUAU_ASSERT(firstTable);
return firstTable;
}
return std::nullopt;
}
TypeId matchLiteralType( TypeId matchLiteralType(
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
@ -257,7 +177,6 @@ TypeId matchLiteralType(
if (tt) if (tt)
{ {
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock); TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
parts.push_back(res); parts.push_back(res);
return arena->addType(UnionType{std::move(parts)}); return arena->addType(UnionType{std::move(parts)});
} }

View file

@ -22,7 +22,6 @@
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit) LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/* /*
@ -1092,7 +1091,7 @@ struct TypeStringifier
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSyntheticErrors && tv.synthetic) if (tv.synthetic)
{ {
state.emit("*error-type<"); state.emit("*error-type<");
stringify(*tv.synthetic); stringify(*tv.synthetic);
@ -1278,7 +1277,7 @@ struct TypePackStringifier
{ {
state.result.error = true; state.result.error = true;
if (FFlag::LuauSyntheticErrors && error.synthetic) if (error.synthetic)
{ {
state.emit("*"); state.emit("*");
stringify(*error.synthetic); stringify(*error.synthetic);
@ -1965,6 +1964,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType); return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
else if constexpr (std::is_same_v<T, TableCheckConstraint>) else if constexpr (std::is_same_v<T, TableCheckConstraint>)
return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType); return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType);
else if constexpr (std::is_same_v<T, SimplifyConstraint>)
return "simplify " + tos(c.ty);
else else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch"); static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
}; };

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,6 @@
#include <string> #include <string>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents) static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
@ -308,8 +307,7 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++]; std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el) if (el)
new (arg) new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), Location()));
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
else else
new (arg) std::optional<AstArgumentName>(); new (arg) std::optional<AstArgumentName>();
} }

View file

@ -30,11 +30,12 @@
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau namespace Luau
{ {
@ -710,14 +711,77 @@ void TypeChecker2::visit(AstStatReturn* ret)
{ {
Scope* scope = findInnermostScope(ret->location); Scope* scope = findInnermostScope(ret->location);
TypePackId expectedRetType = scope->returnType; TypePackId expectedRetType = scope->returnType;
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
{
if (ret->list.size == 0)
{
testIsSubtype(builtinTypes->emptyTypePack, expectedRetType, ret->location);
return;
}
auto [head, _] = extendTypePack(module->internalTypes, builtinTypes, expectedRetType, ret->list.size);
bool isSubtype = true;
std::vector<TypeId> actualHead;
std::optional<TypePackId> actualTail;
for (size_t idx = 0; idx < ret->list.size - 1; idx++)
{
if (idx < head.size())
{
isSubtype &= testPotentialLiteralIsSubtype(ret->list.data[idx], head[idx]);
actualHead.push_back(head[idx]);
}
else
{
actualHead.push_back(lookupType(ret->list.data[idx]));
}
}
// This stanza is deconstructing what constraint generation does to
// return statements. If we have some statement like:
//
// return E0, E1, E2, ... , EN
//
// All expressions *except* the last will be types, and the last can
// potentially be a pack. However, if the last expression is a function
// call or varargs (`...`), then we _could_ have a pack in the final
// position. Additionally, if we have an argument overflow, then we can't
// do anything interesting with subtyping.
//
// _If_ the last argument is not a function call or varargs and we have
// at least an argument underflow, then we grab the last type out of
// the type pack head and use that to check the subtype of
auto lastExpr = ret->list.data[ret->list.size - 1];
if (head.size() < ret->list.size || lastExpr->is<AstExprCall>() || lastExpr->is<AstExprVarargs>())
{
actualTail = lookupPack(lastExpr);
}
else
{
auto lastType = head[ret->list.size - 1];
isSubtype &= testPotentialLiteralIsSubtype(lastExpr, lastType);
actualHead.push_back(lastType);
}
// After all that, we still fire a pack subtype test to determine
// whether we have a well-formed return statement. We only fire
// this if all the previous subtype tests have succeeded, lest
// we double error.
if (isSubtype)
{
auto reconstructedRetType = module->internalTypes.addTypePack(TypePack{std::move(actualHead), std::move(actualTail)});
testIsSubtype(reconstructedRetType, expectedRetType, ret->location);
}
}
else
{
TypeArena* arena = &module->internalTypes; TypeArena* arena = &module->internalTypes;
TypePackId actualRetType = reconstructPack(ret->list, *arena); TypePackId actualRetType = reconstructPack(ret->list, *arena);
testIsSubtype(actualRetType, expectedRetType, ret->location); testIsSubtype(actualRetType, expectedRetType, ret->location);
}
for (AstExpr* expr : ret->list) for (AstExpr* expr : ret->list)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
} }
void TypeChecker2::visit(AstStatExpr* expr) void TypeChecker2::visit(AstStatExpr* expr)
@ -745,7 +809,12 @@ void TypeChecker2::visit(AstStatLocal* local)
TypeId annotationType = lookupAnnotation(var->annotation); TypeId annotationType = lookupAnnotation(var->annotation);
TypeId valueType = value ? lookupType(value) : nullptr; TypeId valueType = value ? lookupType(value) : nullptr;
if (valueType) if (valueType)
{
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
testPotentialLiteralIsSubtype(value, annotationType);
else
testIsSubtype(valueType, annotationType, value->location); testIsSubtype(valueType, annotationType, value->location);
}
visit(var->annotation); visit(var->annotation);
} }
@ -1154,6 +1223,17 @@ void TypeChecker2::visit(AstStatAssign* assign)
continue; continue;
} }
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
{
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
if (testPotentialLiteralIsSubtype(rhs, lhsType))
{
if (std::optional<TypeId> bindingType = getBindingType(lhs))
testPotentialLiteralIsSubtype(rhs, *bindingType);
}
}
else
{
bool ok = testIsSubtype(rhsType, lhsType, rhs->location); bool ok = testIsSubtype(rhsType, lhsType, rhs->location);
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType // If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
@ -1165,6 +1245,7 @@ void TypeChecker2::visit(AstStatAssign* assign)
} }
} }
} }
}
void TypeChecker2::visit(AstStatCompoundAssign* stat) void TypeChecker2::visit(AstStatCompoundAssign* stat)
{ {
@ -1200,13 +1281,18 @@ void TypeChecker2::visit(const AstTypeList* typeList)
void TypeChecker2::visit(AstStatTypeAlias* stat) void TypeChecker2::visit(AstStatTypeAlias* stat)
{ {
// We will not visit type aliases that do not have an associated scope,
// this means that (probably) this was a duplicate type alias or a
// type alias with an illegal name (like `typeof`).
if (FFlag::LuauSkipMalformedTypeAliases && !module->astScopes.contains(stat))
return;
visitGenerics(stat->generics, stat->genericPacks); visitGenerics(stat->generics, stat->genericPacks);
visit(stat->type); visit(stat->type);
} }
void TypeChecker2::visit(AstStatTypeFunction* stat) void TypeChecker2::visit(AstStatTypeFunction* stat)
{ {
if (FFlag::LuauUserTypeFunTypecheck)
visit(stat->body); visit(stat->body);
} }
@ -2857,6 +2943,130 @@ void TypeChecker2::explainError(TypePackId subTy, TypePackId superTy, Location l
reportError(TypePackMismatch{superTy, subTy, reasonings.toString()}, location); reportError(TypePackMismatch{superTy, subTy, reasonings.toString()}, location);
} }
namespace
{
bool isRecord(const AstExprTable::Item& item)
{
return item.kind == AstExprTable::Item::Record || (item.kind == AstExprTable::Item::General && item.key->is<AstExprConstantString>());
}
}
bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType)
{
auto exprType = follow(lookupType(expr));
expectedType = follow(expectedType);
auto exprTable = expr->as<AstExprTable>();
auto exprTableType = get<TableType>(exprType);
auto expectedTableType = get<TableType>(expectedType);
// If we don't have a table or the type of the expression isn't a
// table, then do a normal subtype test.
if (!exprTableType || !exprTable)
return testIsSubtype(exprType, expectedType, expr->location);
// At this point we *know* that the expression is a table and has a specific
// table type, but if there isn't an expected table type we should do something
// slightly different.
if (!expectedTableType)
{
if (auto utv = get<UnionType>(expectedType))
{
std::vector<TypeId> parts{begin(utv), end(utv)};
std::optional<TypeId> tt = extractMatchingTableType(parts, exprType, builtinTypes);
if (tt)
return testPotentialLiteralIsSubtype(expr, *tt);
}
return testIsSubtype(exprType, expectedType, expr->location);
}
Set<std::optional<std::string> > missingKeys{{}};
for (const auto& [name, prop] : expectedTableType->props)
{
LUAU_ASSERT(!prop.isWriteOnly());
auto readTy = *prop.readTy;
if (!isOptional(readTy))
missingKeys.insert(name);
}
bool isArrayLike = false;
if (expectedTableType->indexer)
{
NotNull<Scope> scope{findInnermostScope(expr->location)};
auto result = subtyping->isSubtype(expectedTableType->indexer->indexType, builtinTypes->numberType, scope);
isArrayLike = result.isSubtype;
}
bool isSubtype = true;
for (const auto& item : exprTable->items)
{
if (isRecord(item))
{
const AstArray<char>& s = item.key->as<AstExprConstantString>()->value;
std::string keyStr{s.data, s.data + s.size};
missingKeys.erase(keyStr);
auto expectedIt = expectedTableType->props.find(keyStr);
if (expectedIt == expectedTableType->props.end())
{
if (expectedTableType->indexer)
{
module->astExpectedTypes[item.key] = expectedTableType->indexer->indexType;
module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType;
auto inferredKeyType = module->internalTypes.addType(SingletonType{StringSingleton{keyStr}});
isSubtype &= testIsSubtype(inferredKeyType, expectedTableType->indexer->indexType, item.key->location);
isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType);
}
// If there's not an indexer, then by width subtyping we can just do nothing :)
}
else
{
// TODO: What do we do for write only props?
LUAU_ASSERT(expectedIt->second.readTy);
// Some property is in the expected type: we can test against the specific type.
module->astExpectedTypes[item.value] = *expectedIt->second.readTy;
isSubtype &= testPotentialLiteralIsSubtype(item.value, *expectedIt->second.readTy);
}
}
else if (item.kind == AstExprTable::Item::List)
{
if (!isArrayLike)
{
isSubtype = false;
reportError(UnexpectedArrayLikeTableItem{}, item.value->location);
}
// if the indexer index type is not exactly `number`.
if (expectedTableType->indexer)
{
module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType;
isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType);
}
}
else if (item.kind == AstExprTable::Item::General && expectedTableType->indexer)
{
module->astExpectedTypes[item.key] = expectedTableType->indexer->indexType;
module->astExpectedTypes[item.value] = expectedTableType->indexer->indexResultType;
isSubtype &= testPotentialLiteralIsSubtype(item.key, expectedTableType->indexer->indexType);
isSubtype &= testPotentialLiteralIsSubtype(item.value, expectedTableType->indexer->indexResultType);
}
}
if (!missingKeys.empty())
{
std::vector<Name> temp;
temp.reserve(missingKeys.size());
for (const auto& key : missingKeys)
if (key)
temp.push_back(*key);
reportError(MissingProperties{expectedType, exprType, std::move(temp)}, expr->location);
return false;
}
return isSubtype;
}
bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location) bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location)
{ {
NotNull<Scope> scope{findInnermostScope(location)}; NotNull<Scope> scope{findInnermostScope(location)};

View file

@ -47,24 +47,14 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAGVARIABLE(LuauMetatableTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionImprovements)
LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods)
LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil)
LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength)
LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny)
LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer)
LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil)
LUAU_FASTFLAGVARIABLE(LuauIndexDeferPendingIndexee)
LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
namespace Luau namespace Luau
@ -72,61 +62,6 @@ namespace Luau
using TypeOrTypePackIdSet = DenseHashSet<const void*>; using TypeOrTypePackIdSet = DenseHashSet<const void*>;
struct InstanceCollector_DEPRECATED : TypeOnceVisitor
{
VecDeque<TypeId> tys;
VecDeque<TypePackId> tps;
TypeOrTypePackIdSet shouldGuess{nullptr};
std::vector<TypeId> cyclicInstance;
bool visit(TypeId ty, const TypeFunctionInstanceType&) override
{
// TypeOnceVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(ty);
tys.push_front(ty);
return true;
}
void cycle(TypeId ty) override
{
/// Detected cyclic type pack
TypeId t = follow(ty);
if (get<TypeFunctionInstanceType>(t))
cyclicInstance.push_back(t);
}
bool visit(TypeId ty, const ExternType&) override
{
return false;
}
bool visit(TypePackId tp, const TypeFunctionInstanceTypePack&) override
{
// TypeOnceVisitor performs a depth-first traversal in the absence of
// cycles. This means that by pushing to the front of the queue, we will
// try to reduce deeper instances first if we start with the first thing
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFunctionDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(tp);
tps.push_front(tp);
return true;
}
};
struct InstanceCollector : TypeOnceVisitor struct InstanceCollector : TypeOnceVisitor
{ {
DenseHashSet<TypeId> recordedTys{nullptr}; DenseHashSet<TypeId> recordedTys{nullptr};
@ -546,7 +481,7 @@ struct TypeFunctionReducer
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject)) if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject))
{ {
if (FFlag::LuauNewTypeFunReductionChecks2 && tfit->function->name == "user") if (tfit->function->name == "user")
{ {
UnscopedGenericFinder finder; UnscopedGenericFinder finder;
finder.traverse(subject); finder.traverse(subject);
@ -673,8 +608,6 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
} }
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force) FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{
if (FFlag::LuauNewTypeFunReductionChecks2)
{ {
InstanceCollector collector; InstanceCollector collector;
@ -700,37 +633,8 @@ FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location loc
force force
); );
} }
else
{
InstanceCollector_DEPRECATED collector;
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
}
}
FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force) FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{
if (FFlag::LuauNewTypeFunReductionChecks2)
{ {
InstanceCollector collector; InstanceCollector collector;
@ -756,33 +660,6 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location
force force
); );
} }
else
{
InstanceCollector_DEPRECATED collector;
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
}
}
bool isPending(TypeId ty, ConstraintSolver* solver) bool isPending(TypeId ty, ConstraintSolver* solver)
{ {
@ -934,11 +811,9 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
} }
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones // If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
if (!ctx->typeFunctionRuntime->allowEvaluation || (FFlag::LuauTypeFunResultInAutocomplete && typeFunction->userFuncData.definition->hasErrors)) if (!ctx->typeFunctionRuntime->allowEvaluation || typeFunction->userFuncData.definition->hasErrors)
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}}; return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
if (FFlag::LuauNewTypeFunReductionChecks2)
{
FindUserTypeFunctionBlockers check{ctx}; FindUserTypeFunctionBlockers check{ctx};
for (auto typeParam : typeParams) for (auto typeParam : typeParams)
@ -946,24 +821,12 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
if (!check.blockingTypes.empty()) if (!check.blockingTypes.empty())
return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}}; return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}};
}
else
{
for (auto typeParam : typeParams)
{
TypeId ty = follow(typeParam);
// block if we need to
if (isPending(ty, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
}
}
// Ensure that whole type function environment is registered // Ensure that whole type function environment is registered
for (auto& [name, definition] : typeFunction->userFuncData.environment) for (auto& [name, definition] : typeFunction->userFuncData.environment)
{ {
// Cannot evaluate if a potential dependency couldn't be parsed // Cannot evaluate if a potential dependency couldn't be parsed
if (FFlag::LuauTypeFunResultInAutocomplete && definition.first->hasErrors) if (definition.first->hasErrors)
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}}; return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first)) if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
@ -1179,13 +1042,10 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{}); std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
if (!mmType) if (!mmType)
{
if (FFlag::LuauMetatablesHaveLength)
{ {
// If we have a metatable type with no __len, this means we still have a table with default length function // If we have a metatable type with no __len, this means we still have a table with default length function
if (get<MetatableType>(normalizedOperand)) if (get<MetatableType>(normalizedOperand))
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
}
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
} }
@ -1241,7 +1101,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver)) if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
operandTy = follow(operandTy); operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy); std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1322,7 +1182,7 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
return std::nullopt; return std::nullopt;
// Do not evaluate type functions with parse errors inside // Do not evaluate type functions with parse errors inside
if (FFlag::LuauTypeFunResultInAutocomplete && function->hasErrors) if (function->hasErrors)
return std::nullopt; return std::nullopt;
prepareState(); prepareState();
@ -1918,12 +1778,12 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType); emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
else if (rhsFree && isNumber(lhsTy)) else if (rhsFree && isNumber(lhsTy))
emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType); emplaceType<BoundType>(asMutable(rhsTy), ctx->builtins->numberType);
else if (lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False) else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && lhsFree && ctx->normalizer->isInhabited(rhsTy) != NormalizationResult::False)
{ {
auto c1 = ctx->pushConstraint(EqualityConstraint{lhsTy, rhsTy}); auto c1 = ctx->pushConstraint(EqualityConstraint{lhsTy, rhsTy});
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1); const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
} }
else if (rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False) else if (!FFlag::LuauNoMoreInjectiveTypeFunctions && rhsFree && ctx->normalizer->isInhabited(lhsTy) != NormalizationResult::False)
{ {
auto c1 = ctx->pushConstraint(EqualityConstraint{rhsTy, lhsTy}); auto c1 = ctx->pushConstraint(EqualityConstraint{rhsTy, lhsTy});
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1); const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
@ -2325,8 +2185,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (!crt.found) if (!crt.found)
return {target, {}}; return {target, {}};
if (FFlag::LuauSimplyRefineNotNil)
{
if (auto negation = get<NegationType>(discriminant)) if (auto negation = get<NegationType>(discriminant))
{ {
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType) if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
@ -2335,7 +2193,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {result.result, {}}; return {result.result, {}};
} }
} }
}
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect) if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{ {
@ -2620,19 +2477,11 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
} }
} }
if (FFlag::LuauIntersectNotNil)
{
for (TypeId blockedType : result.blockedTypes) for (TypeId blockedType : result.blockedTypes)
{ {
if (!get<GenericType>(blockedType)) if (!get<GenericType>(blockedType))
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
} }
}
else
{
if (!result.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
}
resultTy = result.result; resultTy = result.result;
} }
@ -2858,7 +2707,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
std::vector<TypeId> singletons; std::vector<TypeId> singletons;
singletons.reserve(keys.size()); singletons.reserve(keys.size());
for (std::string key : keys) for (const std::string& key : keys)
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}})); singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
// If there's only one entry, we don't need a UnionType. // If there's only one entry, we don't need a UnionType.
@ -2927,10 +2776,7 @@ bool searchPropsAndIndexer(
{ {
for (TypeId option : propUnionTy->options) for (TypeId option : propUnionTy->options)
{ {
if (FFlag::LuauIndexTypeFunctionImprovements)
result.insert(follow(option)); result.insert(follow(option));
else
result.insert(option);
} }
} }
else // property is a singular type or intersection type -> we can simply append else // property is a singular type or intersection type -> we can simply append
@ -2943,17 +2789,14 @@ bool searchPropsAndIndexer(
// index into tbl's indexer // index into tbl's indexer
if (tblIndexer) if (tblIndexer)
{ {
TypeId indexType = FFlag::LuauFixCyclicIndexInIndexer ? follow(tblIndexer->indexType) : tblIndexer->indexType; TypeId indexType = follow(tblIndexer->indexType);
if (FFlag::LuauFixCyclicIndexInIndexer)
{
if (auto tfit = get<TypeFunctionInstanceType>(indexType)) if (auto tfit = get<TypeFunctionInstanceType>(indexType))
{ {
// if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot // if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot
if (tfit->function.get() == &builtinTypeFunctions().indexFunc) if (tfit->function.get() == &builtinTypeFunctions().indexFunc)
indexType = follow(tblIndexer->indexResultType); indexType = follow(tblIndexer->indexResultType);
} }
}
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice)) if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
{ {
@ -2964,10 +2807,7 @@ bool searchPropsAndIndexer(
{ {
for (TypeId option : idxResUnionTy->options) for (TypeId option : idxResUnionTy->options)
{ {
if (FFlag::LuauIndexTypeFunctionImprovements)
result.insert(follow(option)); result.insert(follow(option));
else
result.insert(option);
} }
} }
else // indexResultType is a singular type or intersection type -> we can simply append else // indexResultType is a singular type or intersection type -> we can simply append
@ -2980,46 +2820,6 @@ bool searchPropsAndIndexer(
return false; return false;
} }
/* Handles recursion / metamethods of tables and extern types
`isRaw` parameter indicates whether or not we should follow __index metamethods
returns false if property of `ty` could not be found */
bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
{
indexer = follow(indexer);
indexee = follow(indexee);
// we have a table type to try indexing
if (auto tableTy = get<TableType>(indexee))
{
return searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx);
}
// we have a metatable type to try indexing
if (auto metatableTy = get<MetatableType>(indexee))
{
if (auto tableTy = get<TableType>(metatableTy->table))
{
// try finding all properties within the current scope of the table
if (searchPropsAndIndexer(indexer, tableTy->props, tableTy->indexer, result, ctx))
return true;
}
// if the code reached here, it means we weren't able to find all properties -> look into __index metamethod
if (!isRaw)
{
// findMetatableEntry demands the ability to emit errors, so we must give it
// the necessary state to do that, even if we intend to just eat the errors.
ErrorVec dummy;
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, indexee, "__index", Location{});
if (mmType)
return tblIndexInto_DEPRECATED(indexer, *mmType, result, ctx, isRaw);
}
}
return false;
}
bool tblIndexInto( bool tblIndexInto(
TypeId indexer, TypeId indexer,
TypeId indexee, TypeId indexee,
@ -3036,8 +2836,6 @@ bool tblIndexInto(
return false; return false;
seenSet.insert(indexee); seenSet.insert(indexee);
if (FFlag::LuauIndexTypeFunctionFunctionMetamethods)
{
if (auto unionTy = get<UnionType>(indexee)) if (auto unionTy = get<UnionType>(indexee))
{ {
bool res = true; bool res = true;
@ -3080,7 +2878,6 @@ bool tblIndexInto(
result.insert(follow(extracted.head.front())); result.insert(follow(extracted.head.front()));
return true; return true;
} }
}
// we have a table type to try indexing // we have a table type to try indexing
if (auto tableTy = get<TableType>(indexee)) if (auto tableTy = get<TableType>(indexee))
@ -3115,17 +2912,10 @@ bool tblIndexInto(
} }
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw) bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
{
if (FFlag::LuauIndexTypeFunctionImprovements)
{ {
DenseHashSet<TypeId> seenSet{{}}; DenseHashSet<TypeId> seenSet{{}};
return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw); return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw);
} }
else
{
return tblIndexInto_DEPRECATED(indexer, indexee, result, ctx, isRaw);
}
}
/* Vocabulary note: indexee refers to the type that contains the properties, /* Vocabulary note: indexee refers to the type that contains the properties,
indexer refers to the type that is used to access indexee indexer refers to the type that is used to access indexee
@ -3139,7 +2929,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
{ {
TypeId indexeeTy = follow(typeParams.at(0)); TypeId indexeeTy = follow(typeParams.at(0));
if (FFlag::LuauIndexDeferPendingIndexee && isPending(indexeeTy, ctx->solver)) if (isPending(indexeeTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy); std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
@ -3148,12 +2938,9 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (!indexeeNormTy) if (!indexeeNormTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}}; return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (FFlag::LuauIndexAnyIsAny)
{
// if the indexee is `any`, then indexing also gives us `any`. // if the indexee is `any`, then indexing also gives us `any`.
if (indexeeNormTy->shouldSuppressErrors()) if (indexeeNormTy->shouldSuppressErrors())
return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
}
// if we don't have either just tables or just extern types, we've got nothing to index into // if we don't have either just tables or just extern types, we've got nothing to index into
if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes()) if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes())
@ -3254,20 +3041,6 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
} }
} }
if (!FFlag::LuauIndexTypeFunctionImprovements)
{
// Call `follow()` on each element to resolve all Bound types before returning
std::transform(
properties.begin(),
properties.end(),
properties.begin(),
[](TypeId ty)
{
return follow(ty);
}
);
}
// If the type being reduced to is a single type, no need to union // If the type being reduced to is a single type, no need to union
if (properties.size() == 1) if (properties.size() == 1)
return {*properties.begin(), Reduction::MaybeOk, {}, {}}; return {*properties.begin(), Reduction::MaybeOk, {}, {}};
@ -3544,7 +3317,6 @@ TypeFunctionReductionResult<TypeId> weakoptionalTypeFunc(
return {targetTy, Reduction::MaybeOk, {}, {}}; return {targetTy, Reduction::MaybeOk, {}, {}};
} }
BuiltinTypeFunctions::BuiltinTypeFunctions() BuiltinTypeFunctions::BuiltinTypeFunctions()
: userFunc{"user", userDefinedTypeFunction} : userFunc{"user", userDefinedTypeFunction}
, notFunc{"not", notTypeFunction} , notFunc{"not", notTypeFunction}
@ -3640,15 +3412,12 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc); scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc);
} }
if (FFlag::LuauMetatableTypeFunctions)
{
if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults)
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
else else
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc); scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
} }
}
const BuiltinTypeFunctions& builtinTypeFunctions() const BuiltinTypeFunctions& builtinTypeFunctions()
{ {

View file

@ -14,7 +14,6 @@
#include <vector> #include <vector>
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional) LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau namespace Luau
@ -1138,28 +1137,6 @@ static int getFunctionGenerics(lua_State* L)
return 1; return 1;
} }
// Luau: `self:parent() -> type`
// Returns the parent of a class type
static int getClassParent_DEPRECATED(lua_State* L)
{
int argumentCount = lua_gettop(L);
if (argumentCount != 1)
luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount);
TypeFunctionTypeId self = getTypeUserData(L, 1);
auto tfct = get<TypeFunctionExternType>(self);
if (!tfct)
luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str());
// If the parent does not exist, we should return nil
if (!tfct->parent_DEPRECATED)
lua_pushnil(L);
else
allocTypeUserData(L, (*tfct->parent_DEPRECATED)->type);
return 1;
}
// Luau: `self:readparent() -> type` // Luau: `self:readparent() -> type`
// Returns the read type of the class' parent // Returns the read type of the class' parent
static int getReadParent(lua_State* L) static int getReadParent(lua_State* L)
@ -1628,7 +1605,8 @@ void registerTypeUserData(lua_State* L)
{"components", getComponents}, {"components", getComponents},
// Extern type methods // Extern type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED}, {"readparent", getReadParent},
{"writeparent", getWriteParent},
// Function type methods (cont.) // Function type methods (cont.)
{"setgenerics", setFunctionGenerics}, {"setgenerics", setFunctionGenerics},
@ -1638,9 +1616,6 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName}, {"name", getGenericName},
{"ispack", getGenericIsPack}, {"ispack", getGenericIsPack},
// move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr} {nullptr, nullptr}
}; };

View file

@ -19,7 +19,6 @@
// used to control the recursion limit of any operations done by user-defined type functions // used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy` // currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
namespace Luau namespace Luau
{ {
@ -211,7 +210,7 @@ private:
// Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original // Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original
// class // class
target = typeFunctionRuntime->typeArena.allocate( target = typeFunctionRuntime->typeArena.allocate(
TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
); );
} }
else if (auto g = get<GenericType>(ty)) else if (auto g = get<GenericType>(ty))
@ -436,17 +435,10 @@ private:
{ {
TypeFunctionTypeId parent = shallowSerialize(*c1->parent); TypeFunctionTypeId parent = shallowSerialize(*c1->parent);
if (FFlag::LuauTypeFunReadWriteParents)
{
// we don't yet have read/write parents in the type inference engine. // we don't yet have read/write parents in the type inference engine.
c2->readParent = parent; c2->readParent = parent;
c2->writeParent = parent; c2->writeParent = parent;
} }
else
{
c2->parent_DEPRECATED = parent;
}
}
} }
void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2) void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2)

View file

@ -35,7 +35,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauStatForInFix)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
namespace Luau namespace Luau
@ -1319,8 +1318,6 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
// and check them against the parameter types of the iterator function. // and check them against the parameter types of the iterator function.
auto [types, tail] = flatten(callRetPack); auto [types, tail] = flatten(callRetPack);
if (FFlag::LuauStatForInFix)
{
if (!types.empty()) if (!types.empty())
{ {
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end()); std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
@ -1332,12 +1329,6 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatForIn& forin)
} }
} }
else else
{
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
}
else
{ {
// Check if iterator function accepts 0 arguments // Check if iterator function accepts 0 arguments
argPack = addTypePack(TypePack{}); argPack = addTypePack(TypePack{});

View file

@ -6,8 +6,6 @@
#include <stdexcept> #include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauTypePackDetectCycles)
namespace Luau namespace Luau
{ {
@ -149,7 +147,7 @@ TypePackIterator& TypePackIterator::operator++()
currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr; currentTypePack = tp->tail ? log->follow(*tp->tail) : nullptr;
tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr; tp = currentTypePack ? log->getMutable<TypePack>(currentTypePack) : nullptr;
if (FFlag::LuauTypePackDetectCycles && tp) if (tp)
{ {
// Step twice on each iteration to detect cycles // Step twice on each iteration to detect cycles
tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr; tailCycleCheck = tp->tail ? log->follow(*tp->tail) : nullptr;

View file

@ -4,15 +4,17 @@
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/Normalize.h" #include "Luau/Normalize.h"
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/Simplify.h"
#include "Luau/ToString.h" #include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeInfer.h" #include "Luau/TypeInfer.h"
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
namespace Luau namespace Luau
{ {
@ -305,7 +307,7 @@ TypePack extendTypePack(
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail); trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -434,6 +436,25 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, TypeId ty)
ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull<Normalizer> normalizer, TypeId ty)
{ {
if (FFlag::LuauErrorSuppressionTypeFunctionArgs)
{
if (auto tfit = get<TypeFunctionInstanceType>(follow(ty)))
{
for (auto ty : tfit->typeArguments)
{
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType)
return ErrorSuppression::NormalizationFailed;
if (normType->shouldSuppressErrors())
return ErrorSuppression::Suppress;
}
return ErrorSuppression::DoNotSuppress;
}
}
std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty); std::shared_ptr<const NormalizedType> normType = normalizer->normalize(ty);
if (!normType) if (!normType)
@ -570,7 +591,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{ {
LUAU_ASSERT(tp); LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization2) if (!FFlag::LuauNonReentrantGeneralization3)
return; return;
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
@ -587,4 +608,84 @@ void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member."); LUAU_ASSERT(!"No scopes in parent chain had a present `interiorFreeTypePacks` member.");
} }
bool fastIsSubtype(TypeId subTy, TypeId superTy)
{
Relation r = relate(superTy, subTy);
return r == Relation::Coincident || r == Relation::Superset;
}
std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, TypeId exprType, NotNull<BuiltinTypes> builtinTypes)
{
if (tables.empty())
return std::nullopt;
const TableType* exprTable = get<TableType>(follow(exprType));
if (!exprTable)
return std::nullopt;
size_t tableCount = 0;
std::optional<TypeId> firstTable;
for (TypeId ty : tables)
{
ty = follow(ty);
if (auto tt = get<TableType>(ty))
{
// If the expected table has a key whose type is a string or boolean
// singleton and the corresponding exprType property does not match,
// then skip this table.
if (!firstTable)
firstTable = ty;
++tableCount;
for (const auto& [name, expectedProp] : tt->props)
{
if (!expectedProp.readTy)
continue;
const TypeId expectedType = follow(*expectedProp.readTy);
auto st = get<SingletonType>(expectedType);
if (!st)
continue;
auto it = exprTable->props.find(name);
if (it == exprTable->props.end())
continue;
const auto& [_name, exprProp] = *it;
if (!exprProp.readTy)
continue;
const TypeId propType = follow(*exprProp.readTy);
const FreeType* ft = get<FreeType>(propType);
if (ft && get<SingletonType>(ft->lowerBound))
{
if (fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && fastIsSubtype(expectedType, builtinTypes->booleanType))
{
return ty;
}
if (fastIsSubtype(builtinTypes->stringType, ft->upperBound) && fastIsSubtype(expectedType, ft->lowerBound))
{
return ty;
}
}
}
}
}
if (tableCount == 1)
{
LUAU_ASSERT(firstTable);
return firstTable;
}
return std::nullopt;
}
} // namespace Luau } // namespace Luau

View file

@ -18,7 +18,7 @@
#include <optional> #include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau namespace Luau
@ -329,7 +329,7 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
for (TypePackId genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
{ {
if (FFlag::LuauNonReentrantGeneralization2) if (FFlag::LuauNonReentrantGeneralization3)
{ {
if (FFlag::DebugLuauGreedyGeneralization) if (FFlag::DebugLuauGreedyGeneralization)
genericPack = follow(genericPack); genericPack = follow(genericPack);
@ -454,6 +454,12 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
{ {
result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexType, superTable->indexer->indexType);
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
if (FFlag::LuauNonReentrantGeneralization3)
{
// FIXME: We can probably do something more efficient here.
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);
result &= unify(superTable->indexer->indexResultType, subTable->indexer->indexResultType);
}
} }
if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer) if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer)

View file

@ -84,6 +84,6 @@ struct ParseExprResult
CstNodeMap cstNodeMap{nullptr}; CstNodeMap cstNodeMap{nullptr};
}; };
static constexpr const char* kParseNameError = "%error-id%"; inline constexpr const char* kParseNameError = "%error-id%";
} // namespace Luau } // namespace Luau

View file

@ -245,8 +245,6 @@ private:
}; };
TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin); TableIndexerResult parseTableIndexer(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
// Remove with FFlagLuauStoreCSTData2
AstTableIndexer* parseTableIndexer_DEPRECATED(AstTableAccess access, std::optional<Location> accessLocation, Lexeme begin);
AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes); AstTypeOrPack parseFunctionType(bool allowPack, const AstArray<AstAttr*>& attributes);
AstType* parseFunctionTypeTail( AstType* parseFunctionTypeTail(

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
#pragma once #pragma once
#include "Luau/RequireNavigator.h" #include "Luau/RequireNavigator.h"
#include "Luau/RequirerUtils.h" #include "Luau/VfsNavigator.h"
struct FileNavigationContext : Luau::Require::NavigationContext struct FileNavigationContext : Luau::Require::NavigationContext
{ {
@ -13,23 +13,22 @@ struct FileNavigationContext : Luau::Require::NavigationContext
std::string getRequirerIdentifier() const override; std::string getRequirerIdentifier() const override;
// Navigation interface // Navigation interface
NavigateResult reset(const std::string& requirerChunkname) override; NavigateResult reset(const std::string& identifier) override;
NavigateResult jumpToAlias(const std::string& path) override; NavigateResult jumpToAlias(const std::string& path) override;
NavigateResult toParent() override; NavigateResult toParent() override;
NavigateResult toChild(const std::string& component) override; NavigateResult toChild(const std::string& component) override;
bool isConfigPresent() const override; bool isConfigPresent() const override;
std::optional<std::string> getConfig() const override; virtual ConfigBehavior getConfigBehavior() const override;
virtual std::optional<std::string> getAlias(const std::string& alias) const override;
virtual std::optional<std::string> getConfig() const override;
// Custom capabilities // Custom capabilities
bool isModulePresent() const; bool isModulePresent() const;
std::optional<std::string> getIdentifier() const; std::optional<std::string> getIdentifier() const;
std::string path;
std::string suffix;
std::string requirerPath;
private: private:
NavigateResult storePathResult(PathResult result); std::string requirerPath;
VfsNavigator vfs;
}; };

View file

@ -4,6 +4,7 @@
#include "Luau/Require.h" #include "Luau/Require.h"
#include "Luau/Compiler.h" #include "Luau/Compiler.h"
#include "Luau/VfsNavigator.h"
#include "lua.h" #include "lua.h"
@ -29,7 +30,5 @@ struct ReplRequirer
BoolCheck codegenEnabled; BoolCheck codegenEnabled;
Coverage coverageTrack; Coverage coverageTrack;
std::string absPath; VfsNavigator vfs;
std::string relPath;
std::string suffix;
}; };

View file

@ -1,36 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <optional>
#include <string>
#include <string_view>
struct PathResult
{
enum class Status
{
SUCCESS,
AMBIGUOUS,
NOT_FOUND
};
Status status;
std::string absPath;
std::string relPath;
std::string suffix;
};
PathResult getStdInResult();
PathResult getAbsolutePathResult(const std::string& path);
// If given an absolute path, this will implicitly call getAbsolutePathResult.
// Aliases prevent us from solely operating on relative paths, so we need to
// be able to fall back to operating on absolute paths if needed.
PathResult tryGetRelativePathResult(const std::string& path);
PathResult getParent(const std::string& absPath, const std::string& relPath);
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name);
bool isFilePresent(const std::string& path, const std::string& suffix);
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix);

View file

@ -0,0 +1,35 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include <string>
enum class NavigationStatus
{
Success,
Ambiguous,
NotFound
};
class VfsNavigator
{
public:
NavigationStatus resetToStdIn();
NavigationStatus resetToPath(const std::string& path);
NavigationStatus toParent();
NavigationStatus toChild(const std::string& name);
std::string getFilePath() const;
std::string getAbsoluteFilePath() const;
std::string getLuaurcPath() const;
private:
NavigationStatus updateRealPaths();
std::string realPath;
std::string absoluteRealPath;
std::string absolutePathPrefix;
std::string modulePath;
std::string absoluteModulePath;
};

View file

@ -1,47 +1,25 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/AnalyzeRequirer.h" #include "Luau/AnalyzeRequirer.h"
#include "Luau/FileUtils.h"
#include "Luau/RequireNavigator.h" #include "Luau/RequireNavigator.h"
#include "Luau/RequirerUtils.h" #include "Luau/VfsNavigator.h"
#include <string> #include <string>
#include <string_view>
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::storePathResult(PathResult result) static Luau::Require::NavigationContext::NavigateResult convert(NavigationStatus status)
{ {
if (result.status == PathResult::Status::AMBIGUOUS) if (status == NavigationStatus::Success)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
if (result.status == PathResult::Status::NOT_FOUND)
return Luau::Require::NavigationContext::NavigateResult::NotFound;
path = result.absPath;
suffix = result.suffix;
return Luau::Require::NavigationContext::NavigateResult::Success; return Luau::Require::NavigationContext::NavigateResult::Success;
else if (status == NavigationStatus::Ambiguous)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
else
return Luau::Require::NavigationContext::NavigateResult::NotFound;
} }
FileNavigationContext::FileNavigationContext(std::string requirerPath) FileNavigationContext::FileNavigationContext(std::string requirerPath)
: requirerPath(std::move(requirerPath))
{ {
std::string_view path = requirerPath;
if (path.size() >= 10 && path.substr(path.size() - 10) == "/init.luau")
{
path.remove_suffix(10);
}
else if (path.size() >= 9 && path.substr(path.size() - 9) == "/init.lua")
{
path.remove_suffix(9);
}
else if (path.size() >= 5 && path.substr(path.size() - 5) == ".luau")
{
path.remove_suffix(5);
}
else if (path.size() >= 4 && path.substr(path.size() - 4) == ".lua")
{
path.remove_suffix(4);
}
this->requirerPath = path;
} }
std::string FileNavigationContext::getRequirerIdentifier() const std::string FileNavigationContext::getRequirerIdentifier() const
@ -49,51 +27,58 @@ std::string FileNavigationContext::getRequirerIdentifier() const
return requirerPath; return requirerPath;
} }
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& requirerChunkname) Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& identifier)
{ {
if (requirerChunkname == "-") if (identifier == "-")
{ return convert(vfs.resetToStdIn());
return storePathResult(getStdInResult());
}
return storePathResult(tryGetRelativePathResult(requirerChunkname)); return convert(vfs.resetToPath(identifier));
} }
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path) Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path)
{ {
Luau::Require::NavigationContext::NavigateResult result = storePathResult(getAbsolutePathResult(path)); if (!isAbsolutePath(path))
if (result != Luau::Require::NavigationContext::NavigateResult::Success) return Luau::Require::NavigationContext::NavigateResult::NotFound;
return result;
return Luau::Require::NavigationContext::NavigateResult::Success; return convert(vfs.resetToPath(path));
} }
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toParent() Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toParent()
{ {
return storePathResult(getParent(path, path)); return convert(vfs.toParent());
} }
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toChild(const std::string& component) Luau::Require::NavigationContext::NavigateResult FileNavigationContext::toChild(const std::string& component)
{ {
return storePathResult(getChild(path, path, component)); return convert(vfs.toChild(component));
} }
bool FileNavigationContext::isModulePresent() const bool FileNavigationContext::isModulePresent() const
{ {
return isFilePresent(path, suffix); return isFile(vfs.getAbsoluteFilePath());
} }
std::optional<std::string> FileNavigationContext::getIdentifier() const std::optional<std::string> FileNavigationContext::getIdentifier() const
{ {
return path + suffix; return vfs.getAbsoluteFilePath();
} }
bool FileNavigationContext::isConfigPresent() const bool FileNavigationContext::isConfigPresent() const
{ {
return isFilePresent(path, "/.luaurc"); return isFile(vfs.getLuaurcPath());
}
Luau::Require::NavigationContext::ConfigBehavior FileNavigationContext::getConfigBehavior() const
{
return Luau::Require::NavigationContext::ConfigBehavior::GetConfig;
}
std::optional<std::string> FileNavigationContext::getAlias(const std::string& alias) const
{
return std::nullopt;
} }
std::optional<std::string> FileNavigationContext::getConfig() const std::optional<std::string> FileNavigationContext::getConfig() const
{ {
return getFileContents(path, "/.luaurc"); return readFile(vfs.getLuaurcPath());
} }

View file

@ -581,14 +581,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
// new thread needs to have the globals sandboxed // new thread needs to have the globals sandboxed
luaL_sandboxthread(L); luaL_sandboxthread(L);
// ignore file extension when storing module's chunkname std::string chunkname = "@" + normalizePath(name);
std::string chunkname = "@";
std::string_view nameView = name;
if (size_t dotPos = nameView.find_last_of('.'); dotPos != std::string_view::npos)
{
nameView.remove_suffix(nameView.size() - dotPos);
}
chunkname += nameView;
std::string bytecode = Luau::compile(*source, copts()); std::string bytecode = Luau::compile(*source, copts());
int status = 0; int status = 0;

View file

@ -5,8 +5,8 @@
#include "Luau/CodeGenOptions.h" #include "Luau/CodeGenOptions.h"
#include "Luau/FileUtils.h" #include "Luau/FileUtils.h"
#include "Luau/Require.h" #include "Luau/Require.h"
#include "Luau/VfsNavigator.h"
#include "Luau/RequirerUtils.h"
#include "lua.h" #include "lua.h"
#include "lualib.h" #include "lualib.h"
@ -32,19 +32,14 @@ static luarequire_WriteResult write(std::optional<std::string> contents, char* b
return luarequire_WriteResult::WRITE_SUCCESS; return luarequire_WriteResult::WRITE_SUCCESS;
} }
static luarequire_NavigateResult storePathResult(ReplRequirer* req, PathResult result) static luarequire_NavigateResult convert(NavigationStatus status)
{ {
if (result.status == PathResult::Status::AMBIGUOUS) if (status == NavigationStatus::Success)
return NAVIGATE_AMBIGUOUS;
if (result.status == PathResult::Status::NOT_FOUND)
return NAVIGATE_NOT_FOUND;
req->absPath = result.absPath;
req->relPath = result.relPath;
req->suffix = result.suffix;
return NAVIGATE_SUCCESS; return NAVIGATE_SUCCESS;
else if (status == NavigationStatus::Ambiguous)
return NAVIGATE_AMBIGUOUS;
else
return NAVIGATE_NOT_FOUND;
} }
static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname) static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname)
@ -59,13 +54,9 @@ static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requ
std::string chunkname = requirer_chunkname; std::string chunkname = requirer_chunkname;
if (chunkname == "=stdin") if (chunkname == "=stdin")
{ return convert(req->vfs.resetToStdIn());
return storePathResult(req, getStdInResult());
}
else if (!chunkname.empty() && chunkname[0] == '@') else if (!chunkname.empty() && chunkname[0] == '@')
{ return convert(req->vfs.resetToPath(chunkname.substr(1)));
return storePathResult(req, tryGetRelativePathResult(chunkname.substr(1)));
}
return NAVIGATE_NOT_FOUND; return NAVIGATE_NOT_FOUND;
} }
@ -74,62 +65,58 @@ static luarequire_NavigateResult jump_to_alias(lua_State* L, void* ctx, const ch
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
luarequire_NavigateResult result = storePathResult(req, getAbsolutePathResult(path)); if (!isAbsolutePath(path))
if (result != NAVIGATE_SUCCESS) return NAVIGATE_NOT_FOUND;
return result;
// Jumping to an absolute path breaks the relative-require chain. The best return convert(req->vfs.resetToPath(path));
// we can do is to store the absolute path itself.
req->relPath = req->absPath;
return NAVIGATE_SUCCESS;
} }
static luarequire_NavigateResult to_parent(lua_State* L, void* ctx) static luarequire_NavigateResult to_parent(lua_State* L, void* ctx)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return storePathResult(req, getParent(req->absPath, req->relPath)); return convert(req->vfs.toParent());
} }
static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name) static luarequire_NavigateResult to_child(lua_State* L, void* ctx, const char* name)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return storePathResult(req, getChild(req->absPath, req->relPath, name)); return convert(req->vfs.toChild(name));
} }
static bool is_module_present(lua_State* L, void* ctx) static bool is_module_present(lua_State* L, void* ctx)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return isFilePresent(req->absPath, req->suffix); return isFile(req->vfs.getFilePath());
} }
static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_chunkname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write("@" + req->relPath, buffer, buffer_size, size_out); return write("@" + req->vfs.getFilePath(), buffer, buffer_size, size_out);
} }
static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_loadname(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(req->absPath + req->suffix, buffer, buffer_size, size_out); return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out);
} }
static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(req->absPath + req->suffix, buffer, buffer_size, size_out); return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out);
} }
static bool is_config_present(lua_State* L, void* ctx) static bool is_config_present(lua_State* L, void* ctx)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return isFilePresent(req->absPath, "/.luaurc"); return isFile(req->vfs.getLuaurcPath());
} }
static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out)
{ {
ReplRequirer* req = static_cast<ReplRequirer*>(ctx); ReplRequirer* req = static_cast<ReplRequirer*>(ctx);
return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out); return write(readFile(req->vfs.getLuaurcPath()), buffer, buffer_size, size_out);
} }
static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname) static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname)
@ -145,13 +132,26 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname
// new thread needs to have the globals sandboxed // new thread needs to have the globals sandboxed
luaL_sandboxthread(ML); luaL_sandboxthread(ML);
std::optional<std::string> contents = readFile(loadname); bool hadContents = false;
if (!contents) int status = LUA_OK;
luaL_error(L, "could not read file '%s'", loadname);
// Handle C++ RAII objects in a scope which doesn't cause a Luau error
{
std::optional<std::string> contents = readFile(loadname);
hadContents = contents.has_value();
if (contents)
{
// now we can compile & run module on the new thread // now we can compile & run module on the new thread
std::string bytecode = Luau::compile(*contents, req->copts()); std::string bytecode = Luau::compile(*contents, req->copts());
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0) status = luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0);
}
}
if (!hadContents)
luaL_error(L, "could not read file '%s'", loadname);
if (status == 0)
{ {
if (req->codegenEnabled()) if (req->codegenEnabled())
{ {
@ -208,6 +208,7 @@ void requireConfigInit(luarequire_Configuration* config)
config->get_chunkname = get_chunkname; config->get_chunkname = get_chunkname;
config->get_loadname = get_loadname; config->get_loadname = get_loadname;
config->get_cache_key = get_cache_key; config->get_cache_key = get_cache_key;
config->get_alias = nullptr;
config->get_config = get_config; config->get_config = get_config;
config->load = load; config->load = load;
} }

View file

@ -1,119 +0,0 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/RequirerUtils.h"
#include "Luau/FileUtils.h"
#include <algorithm>
#include <string>
#include <string_view>
static std::pair<PathResult::Status, std::string> getSuffixWithAmbiguityCheck(const std::string& path)
{
bool found = false;
std::string suffix;
for (const char* potentialSuffix : {".luau", ".lua"})
{
if (isFile(path + potentialSuffix))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
suffix = potentialSuffix;
found = true;
}
}
if (isDirectory(path))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
for (const char* potentialSuffix : {"/init.luau", "/init.lua"})
{
if (isFile(path + potentialSuffix))
{
if (found)
return {PathResult::Status::AMBIGUOUS, ""};
suffix = potentialSuffix;
found = true;
}
}
found = true;
}
if (!found)
return {PathResult::Status::NOT_FOUND, ""};
return {PathResult::Status::SUCCESS, suffix};
}
static PathResult addSuffix(PathResult partialResult)
{
if (partialResult.status != PathResult::Status::SUCCESS)
return partialResult;
auto [status, suffix] = getSuffixWithAmbiguityCheck(partialResult.absPath);
if (status != PathResult::Status::SUCCESS)
return PathResult{status};
partialResult.suffix = std::move(suffix);
return partialResult;
}
PathResult getStdInResult()
{
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return PathResult{PathResult::Status::NOT_FOUND};
std::replace(cwd->begin(), cwd->end(), '\\', '/');
return PathResult{PathResult::Status::SUCCESS, *cwd + "/stdin", "./stdin", ""};
}
PathResult getAbsolutePathResult(const std::string& path)
{
return addSuffix(PathResult{PathResult::Status::SUCCESS, path});
}
PathResult tryGetRelativePathResult(const std::string& path)
{
if (isAbsolutePath(path))
return getAbsolutePathResult(path);
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return PathResult{PathResult::Status::NOT_FOUND};
std::optional<std::string> resolvedAbsPath = resolvePath(path, *cwd + "/stdin");
if (!resolvedAbsPath)
return PathResult{PathResult::Status::NOT_FOUND};
return addSuffix(PathResult{PathResult::Status::SUCCESS, std::move(*resolvedAbsPath), path});
}
PathResult getParent(const std::string& absPath, const std::string& relPath)
{
std::optional<std::string> parent = getParentPath(absPath);
if (!parent)
return PathResult{PathResult::Status::NOT_FOUND};
return addSuffix(PathResult{PathResult::Status::SUCCESS, *parent, normalizePath(relPath + "/..")});
}
PathResult getChild(const std::string& absPath, const std::string& relPath, const std::string& name)
{
return addSuffix(PathResult{PathResult::Status::SUCCESS, joinPaths(absPath, name), joinPaths(relPath, name)});
}
bool isFilePresent(const std::string& path, const std::string& suffix)
{
return isFile(path + suffix);
}
std::optional<std::string> getFileContents(const std::string& path, const std::string& suffix)
{
return readFile(path + suffix);
}

234
CLI/src/VfsNavigator.cpp Normal file
View file

@ -0,0 +1,234 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/VfsNavigator.h"
#include "Luau/Common.h"
#include "Luau/FileUtils.h"
#include <array>
#include <string>
#include <string_view>
const std::array<std::string_view, 2> kSuffixes = {".luau", ".lua"};
const std::array<std::string_view, 2> kInitSuffixes = {"/init.luau", "/init.lua"};
struct ResolvedRealPath
{
NavigationStatus status;
std::string realPath;
};
static ResolvedRealPath getRealPath(std::string modulePath)
{
bool found = false;
std::string suffix;
size_t lastSlash = modulePath.find_last_of('/');
LUAU_ASSERT(lastSlash != std::string::npos);
std::string lastComponent = modulePath.substr(lastSlash + 1);
if (lastComponent != "init")
{
for (std::string_view potentialSuffix : kSuffixes)
{
if (isFile(modulePath + std::string(potentialSuffix)))
{
if (found)
return {NavigationStatus::Ambiguous};
suffix = potentialSuffix;
found = true;
}
}
}
if (isDirectory(modulePath))
{
if (found)
return {NavigationStatus::Ambiguous};
for (std::string_view potentialSuffix : kInitSuffixes)
{
if (isFile(modulePath + std::string(potentialSuffix)))
{
if (found)
return {NavigationStatus::Ambiguous};
suffix = potentialSuffix;
found = true;
}
}
found = true;
}
if (!found)
return {NavigationStatus::NotFound};
return {NavigationStatus::Success, modulePath + suffix};
}
static bool hasSuffix(std::string_view str, std::string_view suffix)
{
return str.size() >= suffix.size() && str.substr(str.size() - suffix.size()) == suffix;
}
static std::string getModulePath(std::string filePath)
{
for (char& c : filePath)
{
if (c == '\\')
c = '/';
}
std::string_view pathView = filePath;
if (isAbsolutePath(pathView))
{
size_t firstSlash = pathView.find_first_of('/');
LUAU_ASSERT(firstSlash != std::string::npos);
pathView.remove_prefix(firstSlash);
}
for (std::string_view suffix : kInitSuffixes)
{
if (hasSuffix(pathView, suffix))
{
pathView.remove_suffix(suffix.size());
return std::string(pathView);
}
}
for (std::string_view suffix : kSuffixes)
{
if (hasSuffix(pathView, suffix))
{
pathView.remove_suffix(suffix.size());
return std::string(pathView);
}
}
return std::string(pathView);
}
NavigationStatus VfsNavigator::updateRealPaths()
{
ResolvedRealPath result = getRealPath(modulePath);
ResolvedRealPath absoluteResult = getRealPath(absoluteModulePath);
if (result.status != NavigationStatus::Success || absoluteResult.status != NavigationStatus::Success)
return result.status;
realPath = isAbsolutePath(result.realPath) ? absolutePathPrefix + result.realPath : result.realPath;
absoluteRealPath = absolutePathPrefix + absoluteResult.realPath;
return NavigationStatus::Success;
}
NavigationStatus VfsNavigator::resetToStdIn()
{
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return NavigationStatus::NotFound;
realPath = "./stdin";
absoluteRealPath = normalizePath(*cwd + "/stdin");
modulePath = "./stdin";
absoluteModulePath = getModulePath(absoluteRealPath);
size_t firstSlash = absoluteRealPath.find_first_of('/');
LUAU_ASSERT(firstSlash != std::string::npos);
absolutePathPrefix = absoluteRealPath.substr(0, firstSlash);
return NavigationStatus::Success;
}
NavigationStatus VfsNavigator::resetToPath(const std::string& path)
{
std::string normalizedPath = normalizePath(path);
if (isAbsolutePath(normalizedPath))
{
modulePath = getModulePath(normalizedPath);
absoluteModulePath = modulePath;
size_t firstSlash = normalizedPath.find_first_of('/');
LUAU_ASSERT(firstSlash != std::string::npos);
absolutePathPrefix = normalizedPath.substr(0, firstSlash);
}
else
{
std::optional<std::string> cwd = getCurrentWorkingDirectory();
if (!cwd)
return NavigationStatus::NotFound;
modulePath = getModulePath(normalizedPath);
std::string joinedPath = normalizePath(*cwd + "/" + normalizedPath);
absoluteModulePath = getModulePath(joinedPath);
size_t firstSlash = joinedPath.find_first_of('/');
LUAU_ASSERT(firstSlash != std::string::npos);
absolutePathPrefix = joinedPath.substr(0, firstSlash);
}
return updateRealPaths();
}
NavigationStatus VfsNavigator::toParent()
{
if (absoluteModulePath == "/")
return NavigationStatus::NotFound;
size_t numSlashes = 0;
for (char c : absoluteModulePath)
{
if (c == '/')
numSlashes++;
}
LUAU_ASSERT(numSlashes > 0);
if (numSlashes == 1)
return NavigationStatus::NotFound;
modulePath = normalizePath(modulePath + "/..");
absoluteModulePath = normalizePath(absoluteModulePath + "/..");
return updateRealPaths();
}
NavigationStatus VfsNavigator::toChild(const std::string& name)
{
modulePath = normalizePath(modulePath + "/" + name);
absoluteModulePath = normalizePath(absoluteModulePath + "/" + name);
return updateRealPaths();
}
std::string VfsNavigator::getFilePath() const
{
return realPath;
}
std::string VfsNavigator::getAbsoluteFilePath() const
{
return absoluteRealPath;
}
std::string VfsNavigator::getLuaurcPath() const
{
std::string_view directory = realPath;
for (std::string_view suffix : kInitSuffixes)
{
if (hasSuffix(directory, suffix))
{
directory.remove_suffix(suffix.size());
return std::string(directory) + "/.luaurc";
}
}
for (std::string_view suffix : kSuffixes)
{
if (hasSuffix(directory, suffix))
{
directory.remove_suffix(suffix.size());
return std::string(directory) + "/.luaurc";
}
}
return std::string(directory) + "/.luaurc";
}

View file

@ -832,7 +832,7 @@ enum class IrOpKind : uint32_t
// VmExit uses a special value to indicate that pcpos update should be skipped // VmExit uses a special value to indicate that pcpos update should be skipped
// This is only used during type checking at function entry // This is only used during type checking at function entry
constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1; inline constexpr uint32_t kVmExitEntryGuardPc = (1u << 28) - 1;
struct IrOp struct IrOp
{ {
@ -900,7 +900,7 @@ struct IrInst
}; };
// When IrInst operands are used, current instruction index is often required to track lifetime // When IrInst operands are used, current instruction index is often required to track lifetime
constexpr uint32_t kInvalidInstIdx = ~0u; inline constexpr uint32_t kInvalidInstIdx = ~0u;
struct IrInstHash struct IrInstHash
{ {

View file

@ -72,13 +72,13 @@ struct OperandX64
} }
}; };
constexpr OperandX64 addr{SizeX64::none, noreg, 1, noreg, 0}; inline constexpr OperandX64 addr{SizeX64::none, noreg, 1, noreg, 0};
constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0}; inline constexpr OperandX64 byte{SizeX64::byte, noreg, 1, noreg, 0};
constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0}; inline constexpr OperandX64 word{SizeX64::word, noreg, 1, noreg, 0};
constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0}; inline constexpr OperandX64 dword{SizeX64::dword, noreg, 1, noreg, 0};
constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0}; inline constexpr OperandX64 qword{SizeX64::qword, noreg, 1, noreg, 0};
constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0}; inline constexpr OperandX64 xmmword{SizeX64::xmmword, noreg, 1, noreg, 0};
constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0}; inline constexpr OperandX64 ymmword{SizeX64::ymmword, noreg, 1, noreg, 0};
constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale) constexpr OperandX64 operator*(RegisterX64 reg, uint8_t scale)
{ {

View file

@ -47,174 +47,174 @@ constexpr RegisterA64 castReg(KindA64 kind, RegisterA64 reg)
return RegisterA64{kind, reg.index}; return RegisterA64{kind, reg.index};
} }
constexpr RegisterA64 noreg{KindA64::none, 0}; inline constexpr RegisterA64 noreg{KindA64::none, 0};
constexpr RegisterA64 w0{KindA64::w, 0}; inline constexpr RegisterA64 w0{KindA64::w, 0};
constexpr RegisterA64 w1{KindA64::w, 1}; inline constexpr RegisterA64 w1{KindA64::w, 1};
constexpr RegisterA64 w2{KindA64::w, 2}; inline constexpr RegisterA64 w2{KindA64::w, 2};
constexpr RegisterA64 w3{KindA64::w, 3}; inline constexpr RegisterA64 w3{KindA64::w, 3};
constexpr RegisterA64 w4{KindA64::w, 4}; inline constexpr RegisterA64 w4{KindA64::w, 4};
constexpr RegisterA64 w5{KindA64::w, 5}; inline constexpr RegisterA64 w5{KindA64::w, 5};
constexpr RegisterA64 w6{KindA64::w, 6}; inline constexpr RegisterA64 w6{KindA64::w, 6};
constexpr RegisterA64 w7{KindA64::w, 7}; inline constexpr RegisterA64 w7{KindA64::w, 7};
constexpr RegisterA64 w8{KindA64::w, 8}; inline constexpr RegisterA64 w8{KindA64::w, 8};
constexpr RegisterA64 w9{KindA64::w, 9}; inline constexpr RegisterA64 w9{KindA64::w, 9};
constexpr RegisterA64 w10{KindA64::w, 10}; inline constexpr RegisterA64 w10{KindA64::w, 10};
constexpr RegisterA64 w11{KindA64::w, 11}; inline constexpr RegisterA64 w11{KindA64::w, 11};
constexpr RegisterA64 w12{KindA64::w, 12}; inline constexpr RegisterA64 w12{KindA64::w, 12};
constexpr RegisterA64 w13{KindA64::w, 13}; inline constexpr RegisterA64 w13{KindA64::w, 13};
constexpr RegisterA64 w14{KindA64::w, 14}; inline constexpr RegisterA64 w14{KindA64::w, 14};
constexpr RegisterA64 w15{KindA64::w, 15}; inline constexpr RegisterA64 w15{KindA64::w, 15};
constexpr RegisterA64 w16{KindA64::w, 16}; inline constexpr RegisterA64 w16{KindA64::w, 16};
constexpr RegisterA64 w17{KindA64::w, 17}; inline constexpr RegisterA64 w17{KindA64::w, 17};
constexpr RegisterA64 w18{KindA64::w, 18}; inline constexpr RegisterA64 w18{KindA64::w, 18};
constexpr RegisterA64 w19{KindA64::w, 19}; inline constexpr RegisterA64 w19{KindA64::w, 19};
constexpr RegisterA64 w20{KindA64::w, 20}; inline constexpr RegisterA64 w20{KindA64::w, 20};
constexpr RegisterA64 w21{KindA64::w, 21}; inline constexpr RegisterA64 w21{KindA64::w, 21};
constexpr RegisterA64 w22{KindA64::w, 22}; inline constexpr RegisterA64 w22{KindA64::w, 22};
constexpr RegisterA64 w23{KindA64::w, 23}; inline constexpr RegisterA64 w23{KindA64::w, 23};
constexpr RegisterA64 w24{KindA64::w, 24}; inline constexpr RegisterA64 w24{KindA64::w, 24};
constexpr RegisterA64 w25{KindA64::w, 25}; inline constexpr RegisterA64 w25{KindA64::w, 25};
constexpr RegisterA64 w26{KindA64::w, 26}; inline constexpr RegisterA64 w26{KindA64::w, 26};
constexpr RegisterA64 w27{KindA64::w, 27}; inline constexpr RegisterA64 w27{KindA64::w, 27};
constexpr RegisterA64 w28{KindA64::w, 28}; inline constexpr RegisterA64 w28{KindA64::w, 28};
constexpr RegisterA64 w29{KindA64::w, 29}; inline constexpr RegisterA64 w29{KindA64::w, 29};
constexpr RegisterA64 w30{KindA64::w, 30}; inline constexpr RegisterA64 w30{KindA64::w, 30};
constexpr RegisterA64 wzr{KindA64::w, 31}; inline constexpr RegisterA64 wzr{KindA64::w, 31};
constexpr RegisterA64 x0{KindA64::x, 0}; inline constexpr RegisterA64 x0{KindA64::x, 0};
constexpr RegisterA64 x1{KindA64::x, 1}; inline constexpr RegisterA64 x1{KindA64::x, 1};
constexpr RegisterA64 x2{KindA64::x, 2}; inline constexpr RegisterA64 x2{KindA64::x, 2};
constexpr RegisterA64 x3{KindA64::x, 3}; inline constexpr RegisterA64 x3{KindA64::x, 3};
constexpr RegisterA64 x4{KindA64::x, 4}; inline constexpr RegisterA64 x4{KindA64::x, 4};
constexpr RegisterA64 x5{KindA64::x, 5}; inline constexpr RegisterA64 x5{KindA64::x, 5};
constexpr RegisterA64 x6{KindA64::x, 6}; inline constexpr RegisterA64 x6{KindA64::x, 6};
constexpr RegisterA64 x7{KindA64::x, 7}; inline constexpr RegisterA64 x7{KindA64::x, 7};
constexpr RegisterA64 x8{KindA64::x, 8}; inline constexpr RegisterA64 x8{KindA64::x, 8};
constexpr RegisterA64 x9{KindA64::x, 9}; inline constexpr RegisterA64 x9{KindA64::x, 9};
constexpr RegisterA64 x10{KindA64::x, 10}; inline constexpr RegisterA64 x10{KindA64::x, 10};
constexpr RegisterA64 x11{KindA64::x, 11}; inline constexpr RegisterA64 x11{KindA64::x, 11};
constexpr RegisterA64 x12{KindA64::x, 12}; inline constexpr RegisterA64 x12{KindA64::x, 12};
constexpr RegisterA64 x13{KindA64::x, 13}; inline constexpr RegisterA64 x13{KindA64::x, 13};
constexpr RegisterA64 x14{KindA64::x, 14}; inline constexpr RegisterA64 x14{KindA64::x, 14};
constexpr RegisterA64 x15{KindA64::x, 15}; inline constexpr RegisterA64 x15{KindA64::x, 15};
constexpr RegisterA64 x16{KindA64::x, 16}; inline constexpr RegisterA64 x16{KindA64::x, 16};
constexpr RegisterA64 x17{KindA64::x, 17}; inline constexpr RegisterA64 x17{KindA64::x, 17};
constexpr RegisterA64 x18{KindA64::x, 18}; inline constexpr RegisterA64 x18{KindA64::x, 18};
constexpr RegisterA64 x19{KindA64::x, 19}; inline constexpr RegisterA64 x19{KindA64::x, 19};
constexpr RegisterA64 x20{KindA64::x, 20}; inline constexpr RegisterA64 x20{KindA64::x, 20};
constexpr RegisterA64 x21{KindA64::x, 21}; inline constexpr RegisterA64 x21{KindA64::x, 21};
constexpr RegisterA64 x22{KindA64::x, 22}; inline constexpr RegisterA64 x22{KindA64::x, 22};
constexpr RegisterA64 x23{KindA64::x, 23}; inline constexpr RegisterA64 x23{KindA64::x, 23};
constexpr RegisterA64 x24{KindA64::x, 24}; inline constexpr RegisterA64 x24{KindA64::x, 24};
constexpr RegisterA64 x25{KindA64::x, 25}; inline constexpr RegisterA64 x25{KindA64::x, 25};
constexpr RegisterA64 x26{KindA64::x, 26}; inline constexpr RegisterA64 x26{KindA64::x, 26};
constexpr RegisterA64 x27{KindA64::x, 27}; inline constexpr RegisterA64 x27{KindA64::x, 27};
constexpr RegisterA64 x28{KindA64::x, 28}; inline constexpr RegisterA64 x28{KindA64::x, 28};
constexpr RegisterA64 x29{KindA64::x, 29}; inline constexpr RegisterA64 x29{KindA64::x, 29};
constexpr RegisterA64 x30{KindA64::x, 30}; inline constexpr RegisterA64 x30{KindA64::x, 30};
constexpr RegisterA64 xzr{KindA64::x, 31}; inline constexpr RegisterA64 xzr{KindA64::x, 31};
constexpr RegisterA64 sp{KindA64::none, 31}; inline constexpr RegisterA64 sp{KindA64::none, 31};
constexpr RegisterA64 s0{KindA64::s, 0}; inline constexpr RegisterA64 s0{KindA64::s, 0};
constexpr RegisterA64 s1{KindA64::s, 1}; inline constexpr RegisterA64 s1{KindA64::s, 1};
constexpr RegisterA64 s2{KindA64::s, 2}; inline constexpr RegisterA64 s2{KindA64::s, 2};
constexpr RegisterA64 s3{KindA64::s, 3}; inline constexpr RegisterA64 s3{KindA64::s, 3};
constexpr RegisterA64 s4{KindA64::s, 4}; inline constexpr RegisterA64 s4{KindA64::s, 4};
constexpr RegisterA64 s5{KindA64::s, 5}; inline constexpr RegisterA64 s5{KindA64::s, 5};
constexpr RegisterA64 s6{KindA64::s, 6}; inline constexpr RegisterA64 s6{KindA64::s, 6};
constexpr RegisterA64 s7{KindA64::s, 7}; inline constexpr RegisterA64 s7{KindA64::s, 7};
constexpr RegisterA64 s8{KindA64::s, 8}; inline constexpr RegisterA64 s8{KindA64::s, 8};
constexpr RegisterA64 s9{KindA64::s, 9}; inline constexpr RegisterA64 s9{KindA64::s, 9};
constexpr RegisterA64 s10{KindA64::s, 10}; inline constexpr RegisterA64 s10{KindA64::s, 10};
constexpr RegisterA64 s11{KindA64::s, 11}; inline constexpr RegisterA64 s11{KindA64::s, 11};
constexpr RegisterA64 s12{KindA64::s, 12}; inline constexpr RegisterA64 s12{KindA64::s, 12};
constexpr RegisterA64 s13{KindA64::s, 13}; inline constexpr RegisterA64 s13{KindA64::s, 13};
constexpr RegisterA64 s14{KindA64::s, 14}; inline constexpr RegisterA64 s14{KindA64::s, 14};
constexpr RegisterA64 s15{KindA64::s, 15}; inline constexpr RegisterA64 s15{KindA64::s, 15};
constexpr RegisterA64 s16{KindA64::s, 16}; inline constexpr RegisterA64 s16{KindA64::s, 16};
constexpr RegisterA64 s17{KindA64::s, 17}; inline constexpr RegisterA64 s17{KindA64::s, 17};
constexpr RegisterA64 s18{KindA64::s, 18}; inline constexpr RegisterA64 s18{KindA64::s, 18};
constexpr RegisterA64 s19{KindA64::s, 19}; inline constexpr RegisterA64 s19{KindA64::s, 19};
constexpr RegisterA64 s20{KindA64::s, 20}; inline constexpr RegisterA64 s20{KindA64::s, 20};
constexpr RegisterA64 s21{KindA64::s, 21}; inline constexpr RegisterA64 s21{KindA64::s, 21};
constexpr RegisterA64 s22{KindA64::s, 22}; inline constexpr RegisterA64 s22{KindA64::s, 22};
constexpr RegisterA64 s23{KindA64::s, 23}; inline constexpr RegisterA64 s23{KindA64::s, 23};
constexpr RegisterA64 s24{KindA64::s, 24}; inline constexpr RegisterA64 s24{KindA64::s, 24};
constexpr RegisterA64 s25{KindA64::s, 25}; inline constexpr RegisterA64 s25{KindA64::s, 25};
constexpr RegisterA64 s26{KindA64::s, 26}; inline constexpr RegisterA64 s26{KindA64::s, 26};
constexpr RegisterA64 s27{KindA64::s, 27}; inline constexpr RegisterA64 s27{KindA64::s, 27};
constexpr RegisterA64 s28{KindA64::s, 28}; inline constexpr RegisterA64 s28{KindA64::s, 28};
constexpr RegisterA64 s29{KindA64::s, 29}; inline constexpr RegisterA64 s29{KindA64::s, 29};
constexpr RegisterA64 s30{KindA64::s, 30}; inline constexpr RegisterA64 s30{KindA64::s, 30};
constexpr RegisterA64 s31{KindA64::s, 31}; inline constexpr RegisterA64 s31{KindA64::s, 31};
constexpr RegisterA64 d0{KindA64::d, 0}; inline constexpr RegisterA64 d0{KindA64::d, 0};
constexpr RegisterA64 d1{KindA64::d, 1}; inline constexpr RegisterA64 d1{KindA64::d, 1};
constexpr RegisterA64 d2{KindA64::d, 2}; inline constexpr RegisterA64 d2{KindA64::d, 2};
constexpr RegisterA64 d3{KindA64::d, 3}; inline constexpr RegisterA64 d3{KindA64::d, 3};
constexpr RegisterA64 d4{KindA64::d, 4}; inline constexpr RegisterA64 d4{KindA64::d, 4};
constexpr RegisterA64 d5{KindA64::d, 5}; inline constexpr RegisterA64 d5{KindA64::d, 5};
constexpr RegisterA64 d6{KindA64::d, 6}; inline constexpr RegisterA64 d6{KindA64::d, 6};
constexpr RegisterA64 d7{KindA64::d, 7}; inline constexpr RegisterA64 d7{KindA64::d, 7};
constexpr RegisterA64 d8{KindA64::d, 8}; inline constexpr RegisterA64 d8{KindA64::d, 8};
constexpr RegisterA64 d9{KindA64::d, 9}; inline constexpr RegisterA64 d9{KindA64::d, 9};
constexpr RegisterA64 d10{KindA64::d, 10}; inline constexpr RegisterA64 d10{KindA64::d, 10};
constexpr RegisterA64 d11{KindA64::d, 11}; inline constexpr RegisterA64 d11{KindA64::d, 11};
constexpr RegisterA64 d12{KindA64::d, 12}; inline constexpr RegisterA64 d12{KindA64::d, 12};
constexpr RegisterA64 d13{KindA64::d, 13}; inline constexpr RegisterA64 d13{KindA64::d, 13};
constexpr RegisterA64 d14{KindA64::d, 14}; inline constexpr RegisterA64 d14{KindA64::d, 14};
constexpr RegisterA64 d15{KindA64::d, 15}; inline constexpr RegisterA64 d15{KindA64::d, 15};
constexpr RegisterA64 d16{KindA64::d, 16}; inline constexpr RegisterA64 d16{KindA64::d, 16};
constexpr RegisterA64 d17{KindA64::d, 17}; inline constexpr RegisterA64 d17{KindA64::d, 17};
constexpr RegisterA64 d18{KindA64::d, 18}; inline constexpr RegisterA64 d18{KindA64::d, 18};
constexpr RegisterA64 d19{KindA64::d, 19}; inline constexpr RegisterA64 d19{KindA64::d, 19};
constexpr RegisterA64 d20{KindA64::d, 20}; inline constexpr RegisterA64 d20{KindA64::d, 20};
constexpr RegisterA64 d21{KindA64::d, 21}; inline constexpr RegisterA64 d21{KindA64::d, 21};
constexpr RegisterA64 d22{KindA64::d, 22}; inline constexpr RegisterA64 d22{KindA64::d, 22};
constexpr RegisterA64 d23{KindA64::d, 23}; inline constexpr RegisterA64 d23{KindA64::d, 23};
constexpr RegisterA64 d24{KindA64::d, 24}; inline constexpr RegisterA64 d24{KindA64::d, 24};
constexpr RegisterA64 d25{KindA64::d, 25}; inline constexpr RegisterA64 d25{KindA64::d, 25};
constexpr RegisterA64 d26{KindA64::d, 26}; inline constexpr RegisterA64 d26{KindA64::d, 26};
constexpr RegisterA64 d27{KindA64::d, 27}; inline constexpr RegisterA64 d27{KindA64::d, 27};
constexpr RegisterA64 d28{KindA64::d, 28}; inline constexpr RegisterA64 d28{KindA64::d, 28};
constexpr RegisterA64 d29{KindA64::d, 29}; inline constexpr RegisterA64 d29{KindA64::d, 29};
constexpr RegisterA64 d30{KindA64::d, 30}; inline constexpr RegisterA64 d30{KindA64::d, 30};
constexpr RegisterA64 d31{KindA64::d, 31}; inline constexpr RegisterA64 d31{KindA64::d, 31};
constexpr RegisterA64 q0{KindA64::q, 0}; inline constexpr RegisterA64 q0{KindA64::q, 0};
constexpr RegisterA64 q1{KindA64::q, 1}; inline constexpr RegisterA64 q1{KindA64::q, 1};
constexpr RegisterA64 q2{KindA64::q, 2}; inline constexpr RegisterA64 q2{KindA64::q, 2};
constexpr RegisterA64 q3{KindA64::q, 3}; inline constexpr RegisterA64 q3{KindA64::q, 3};
constexpr RegisterA64 q4{KindA64::q, 4}; inline constexpr RegisterA64 q4{KindA64::q, 4};
constexpr RegisterA64 q5{KindA64::q, 5}; inline constexpr RegisterA64 q5{KindA64::q, 5};
constexpr RegisterA64 q6{KindA64::q, 6}; inline constexpr RegisterA64 q6{KindA64::q, 6};
constexpr RegisterA64 q7{KindA64::q, 7}; inline constexpr RegisterA64 q7{KindA64::q, 7};
constexpr RegisterA64 q8{KindA64::q, 8}; inline constexpr RegisterA64 q8{KindA64::q, 8};
constexpr RegisterA64 q9{KindA64::q, 9}; inline constexpr RegisterA64 q9{KindA64::q, 9};
constexpr RegisterA64 q10{KindA64::q, 10}; inline constexpr RegisterA64 q10{KindA64::q, 10};
constexpr RegisterA64 q11{KindA64::q, 11}; inline constexpr RegisterA64 q11{KindA64::q, 11};
constexpr RegisterA64 q12{KindA64::q, 12}; inline constexpr RegisterA64 q12{KindA64::q, 12};
constexpr RegisterA64 q13{KindA64::q, 13}; inline constexpr RegisterA64 q13{KindA64::q, 13};
constexpr RegisterA64 q14{KindA64::q, 14}; inline constexpr RegisterA64 q14{KindA64::q, 14};
constexpr RegisterA64 q15{KindA64::q, 15}; inline constexpr RegisterA64 q15{KindA64::q, 15};
constexpr RegisterA64 q16{KindA64::q, 16}; inline constexpr RegisterA64 q16{KindA64::q, 16};
constexpr RegisterA64 q17{KindA64::q, 17}; inline constexpr RegisterA64 q17{KindA64::q, 17};
constexpr RegisterA64 q18{KindA64::q, 18}; inline constexpr RegisterA64 q18{KindA64::q, 18};
constexpr RegisterA64 q19{KindA64::q, 19}; inline constexpr RegisterA64 q19{KindA64::q, 19};
constexpr RegisterA64 q20{KindA64::q, 20}; inline constexpr RegisterA64 q20{KindA64::q, 20};
constexpr RegisterA64 q21{KindA64::q, 21}; inline constexpr RegisterA64 q21{KindA64::q, 21};
constexpr RegisterA64 q22{KindA64::q, 22}; inline constexpr RegisterA64 q22{KindA64::q, 22};
constexpr RegisterA64 q23{KindA64::q, 23}; inline constexpr RegisterA64 q23{KindA64::q, 23};
constexpr RegisterA64 q24{KindA64::q, 24}; inline constexpr RegisterA64 q24{KindA64::q, 24};
constexpr RegisterA64 q25{KindA64::q, 25}; inline constexpr RegisterA64 q25{KindA64::q, 25};
constexpr RegisterA64 q26{KindA64::q, 26}; inline constexpr RegisterA64 q26{KindA64::q, 26};
constexpr RegisterA64 q27{KindA64::q, 27}; inline constexpr RegisterA64 q27{KindA64::q, 27};
constexpr RegisterA64 q28{KindA64::q, 28}; inline constexpr RegisterA64 q28{KindA64::q, 28};
constexpr RegisterA64 q29{KindA64::q, 29}; inline constexpr RegisterA64 q29{KindA64::q, 29};
constexpr RegisterA64 q30{KindA64::q, 30}; inline constexpr RegisterA64 q30{KindA64::q, 30};
constexpr RegisterA64 q31{KindA64::q, 31}; inline constexpr RegisterA64 q31{KindA64::q, 31};
} // namespace A64 } // namespace A64
} // namespace CodeGen } // namespace CodeGen

View file

@ -39,93 +39,93 @@ struct RegisterX64
} }
}; };
constexpr RegisterX64 noreg{SizeX64::none, 16}; inline constexpr RegisterX64 noreg{SizeX64::none, 16};
constexpr RegisterX64 rip{SizeX64::none, 0}; inline constexpr RegisterX64 rip{SizeX64::none, 0};
constexpr RegisterX64 al{SizeX64::byte, 0}; inline constexpr RegisterX64 al{SizeX64::byte, 0};
constexpr RegisterX64 cl{SizeX64::byte, 1}; inline constexpr RegisterX64 cl{SizeX64::byte, 1};
constexpr RegisterX64 dl{SizeX64::byte, 2}; inline constexpr RegisterX64 dl{SizeX64::byte, 2};
constexpr RegisterX64 bl{SizeX64::byte, 3}; inline constexpr RegisterX64 bl{SizeX64::byte, 3};
constexpr RegisterX64 spl{SizeX64::byte, 4}; inline constexpr RegisterX64 spl{SizeX64::byte, 4};
constexpr RegisterX64 bpl{SizeX64::byte, 5}; inline constexpr RegisterX64 bpl{SizeX64::byte, 5};
constexpr RegisterX64 sil{SizeX64::byte, 6}; inline constexpr RegisterX64 sil{SizeX64::byte, 6};
constexpr RegisterX64 dil{SizeX64::byte, 7}; inline constexpr RegisterX64 dil{SizeX64::byte, 7};
constexpr RegisterX64 r8b{SizeX64::byte, 8}; inline constexpr RegisterX64 r8b{SizeX64::byte, 8};
constexpr RegisterX64 r9b{SizeX64::byte, 9}; inline constexpr RegisterX64 r9b{SizeX64::byte, 9};
constexpr RegisterX64 r10b{SizeX64::byte, 10}; inline constexpr RegisterX64 r10b{SizeX64::byte, 10};
constexpr RegisterX64 r11b{SizeX64::byte, 11}; inline constexpr RegisterX64 r11b{SizeX64::byte, 11};
constexpr RegisterX64 r12b{SizeX64::byte, 12}; inline constexpr RegisterX64 r12b{SizeX64::byte, 12};
constexpr RegisterX64 r13b{SizeX64::byte, 13}; inline constexpr RegisterX64 r13b{SizeX64::byte, 13};
constexpr RegisterX64 r14b{SizeX64::byte, 14}; inline constexpr RegisterX64 r14b{SizeX64::byte, 14};
constexpr RegisterX64 r15b{SizeX64::byte, 15}; inline constexpr RegisterX64 r15b{SizeX64::byte, 15};
constexpr RegisterX64 eax{SizeX64::dword, 0}; inline constexpr RegisterX64 eax{SizeX64::dword, 0};
constexpr RegisterX64 ecx{SizeX64::dword, 1}; inline constexpr RegisterX64 ecx{SizeX64::dword, 1};
constexpr RegisterX64 edx{SizeX64::dword, 2}; inline constexpr RegisterX64 edx{SizeX64::dword, 2};
constexpr RegisterX64 ebx{SizeX64::dword, 3}; inline constexpr RegisterX64 ebx{SizeX64::dword, 3};
constexpr RegisterX64 esp{SizeX64::dword, 4}; inline constexpr RegisterX64 esp{SizeX64::dword, 4};
constexpr RegisterX64 ebp{SizeX64::dword, 5}; inline constexpr RegisterX64 ebp{SizeX64::dword, 5};
constexpr RegisterX64 esi{SizeX64::dword, 6}; inline constexpr RegisterX64 esi{SizeX64::dword, 6};
constexpr RegisterX64 edi{SizeX64::dword, 7}; inline constexpr RegisterX64 edi{SizeX64::dword, 7};
constexpr RegisterX64 r8d{SizeX64::dword, 8}; inline constexpr RegisterX64 r8d{SizeX64::dword, 8};
constexpr RegisterX64 r9d{SizeX64::dword, 9}; inline constexpr RegisterX64 r9d{SizeX64::dword, 9};
constexpr RegisterX64 r10d{SizeX64::dword, 10}; inline constexpr RegisterX64 r10d{SizeX64::dword, 10};
constexpr RegisterX64 r11d{SizeX64::dword, 11}; inline constexpr RegisterX64 r11d{SizeX64::dword, 11};
constexpr RegisterX64 r12d{SizeX64::dword, 12}; inline constexpr RegisterX64 r12d{SizeX64::dword, 12};
constexpr RegisterX64 r13d{SizeX64::dword, 13}; inline constexpr RegisterX64 r13d{SizeX64::dword, 13};
constexpr RegisterX64 r14d{SizeX64::dword, 14}; inline constexpr RegisterX64 r14d{SizeX64::dword, 14};
constexpr RegisterX64 r15d{SizeX64::dword, 15}; inline constexpr RegisterX64 r15d{SizeX64::dword, 15};
constexpr RegisterX64 rax{SizeX64::qword, 0}; inline constexpr RegisterX64 rax{SizeX64::qword, 0};
constexpr RegisterX64 rcx{SizeX64::qword, 1}; inline constexpr RegisterX64 rcx{SizeX64::qword, 1};
constexpr RegisterX64 rdx{SizeX64::qword, 2}; inline constexpr RegisterX64 rdx{SizeX64::qword, 2};
constexpr RegisterX64 rbx{SizeX64::qword, 3}; inline constexpr RegisterX64 rbx{SizeX64::qword, 3};
constexpr RegisterX64 rsp{SizeX64::qword, 4}; inline constexpr RegisterX64 rsp{SizeX64::qword, 4};
constexpr RegisterX64 rbp{SizeX64::qword, 5}; inline constexpr RegisterX64 rbp{SizeX64::qword, 5};
constexpr RegisterX64 rsi{SizeX64::qword, 6}; inline constexpr RegisterX64 rsi{SizeX64::qword, 6};
constexpr RegisterX64 rdi{SizeX64::qword, 7}; inline constexpr RegisterX64 rdi{SizeX64::qword, 7};
constexpr RegisterX64 r8{SizeX64::qword, 8}; inline constexpr RegisterX64 r8{SizeX64::qword, 8};
constexpr RegisterX64 r9{SizeX64::qword, 9}; inline constexpr RegisterX64 r9{SizeX64::qword, 9};
constexpr RegisterX64 r10{SizeX64::qword, 10}; inline constexpr RegisterX64 r10{SizeX64::qword, 10};
constexpr RegisterX64 r11{SizeX64::qword, 11}; inline constexpr RegisterX64 r11{SizeX64::qword, 11};
constexpr RegisterX64 r12{SizeX64::qword, 12}; inline constexpr RegisterX64 r12{SizeX64::qword, 12};
constexpr RegisterX64 r13{SizeX64::qword, 13}; inline constexpr RegisterX64 r13{SizeX64::qword, 13};
constexpr RegisterX64 r14{SizeX64::qword, 14}; inline constexpr RegisterX64 r14{SizeX64::qword, 14};
constexpr RegisterX64 r15{SizeX64::qword, 15}; inline constexpr RegisterX64 r15{SizeX64::qword, 15};
constexpr RegisterX64 xmm0{SizeX64::xmmword, 0}; inline constexpr RegisterX64 xmm0{SizeX64::xmmword, 0};
constexpr RegisterX64 xmm1{SizeX64::xmmword, 1}; inline constexpr RegisterX64 xmm1{SizeX64::xmmword, 1};
constexpr RegisterX64 xmm2{SizeX64::xmmword, 2}; inline constexpr RegisterX64 xmm2{SizeX64::xmmword, 2};
constexpr RegisterX64 xmm3{SizeX64::xmmword, 3}; inline constexpr RegisterX64 xmm3{SizeX64::xmmword, 3};
constexpr RegisterX64 xmm4{SizeX64::xmmword, 4}; inline constexpr RegisterX64 xmm4{SizeX64::xmmword, 4};
constexpr RegisterX64 xmm5{SizeX64::xmmword, 5}; inline constexpr RegisterX64 xmm5{SizeX64::xmmword, 5};
constexpr RegisterX64 xmm6{SizeX64::xmmword, 6}; inline constexpr RegisterX64 xmm6{SizeX64::xmmword, 6};
constexpr RegisterX64 xmm7{SizeX64::xmmword, 7}; inline constexpr RegisterX64 xmm7{SizeX64::xmmword, 7};
constexpr RegisterX64 xmm8{SizeX64::xmmword, 8}; inline constexpr RegisterX64 xmm8{SizeX64::xmmword, 8};
constexpr RegisterX64 xmm9{SizeX64::xmmword, 9}; inline constexpr RegisterX64 xmm9{SizeX64::xmmword, 9};
constexpr RegisterX64 xmm10{SizeX64::xmmword, 10}; inline constexpr RegisterX64 xmm10{SizeX64::xmmword, 10};
constexpr RegisterX64 xmm11{SizeX64::xmmword, 11}; inline constexpr RegisterX64 xmm11{SizeX64::xmmword, 11};
constexpr RegisterX64 xmm12{SizeX64::xmmword, 12}; inline constexpr RegisterX64 xmm12{SizeX64::xmmword, 12};
constexpr RegisterX64 xmm13{SizeX64::xmmword, 13}; inline constexpr RegisterX64 xmm13{SizeX64::xmmword, 13};
constexpr RegisterX64 xmm14{SizeX64::xmmword, 14}; inline constexpr RegisterX64 xmm14{SizeX64::xmmword, 14};
constexpr RegisterX64 xmm15{SizeX64::xmmword, 15}; inline constexpr RegisterX64 xmm15{SizeX64::xmmword, 15};
constexpr RegisterX64 ymm0{SizeX64::ymmword, 0}; inline constexpr RegisterX64 ymm0{SizeX64::ymmword, 0};
constexpr RegisterX64 ymm1{SizeX64::ymmword, 1}; inline constexpr RegisterX64 ymm1{SizeX64::ymmword, 1};
constexpr RegisterX64 ymm2{SizeX64::ymmword, 2}; inline constexpr RegisterX64 ymm2{SizeX64::ymmword, 2};
constexpr RegisterX64 ymm3{SizeX64::ymmword, 3}; inline constexpr RegisterX64 ymm3{SizeX64::ymmword, 3};
constexpr RegisterX64 ymm4{SizeX64::ymmword, 4}; inline constexpr RegisterX64 ymm4{SizeX64::ymmword, 4};
constexpr RegisterX64 ymm5{SizeX64::ymmword, 5}; inline constexpr RegisterX64 ymm5{SizeX64::ymmword, 5};
constexpr RegisterX64 ymm6{SizeX64::ymmword, 6}; inline constexpr RegisterX64 ymm6{SizeX64::ymmword, 6};
constexpr RegisterX64 ymm7{SizeX64::ymmword, 7}; inline constexpr RegisterX64 ymm7{SizeX64::ymmword, 7};
constexpr RegisterX64 ymm8{SizeX64::ymmword, 8}; inline constexpr RegisterX64 ymm8{SizeX64::ymmword, 8};
constexpr RegisterX64 ymm9{SizeX64::ymmword, 9}; inline constexpr RegisterX64 ymm9{SizeX64::ymmword, 9};
constexpr RegisterX64 ymm10{SizeX64::ymmword, 10}; inline constexpr RegisterX64 ymm10{SizeX64::ymmword, 10};
constexpr RegisterX64 ymm11{SizeX64::ymmword, 11}; inline constexpr RegisterX64 ymm11{SizeX64::ymmword, 11};
constexpr RegisterX64 ymm12{SizeX64::ymmword, 12}; inline constexpr RegisterX64 ymm12{SizeX64::ymmword, 12};
constexpr RegisterX64 ymm13{SizeX64::ymmword, 13}; inline constexpr RegisterX64 ymm13{SizeX64::ymmword, 13};
constexpr RegisterX64 ymm14{SizeX64::ymmword, 14}; inline constexpr RegisterX64 ymm14{SizeX64::ymmword, 14};
constexpr RegisterX64 ymm15{SizeX64::ymmword, 15}; inline constexpr RegisterX64 ymm15{SizeX64::ymmword, 15};
constexpr RegisterX64 byteReg(RegisterX64 reg) constexpr RegisterX64 byteReg(RegisterX64 reg)
{ {

View file

@ -16,7 +16,7 @@ namespace CodeGen
{ {
// This value is used in 'finishFunction' to mark the function that spans to the end of the whole code block // This value is used in 'finishFunction' to mark the function that spans to the end of the whole code block
static uint32_t kFullBlockFunction = ~0u; inline constexpr uint32_t kFullBlockFunction = ~0u;
class UnwindBuilder class UnwindBuilder
{ {

View file

@ -27,26 +27,26 @@ namespace A64
// Data that is very common to access is placed in non-volatile registers: // Data that is very common to access is placed in non-volatile registers:
// 1. Constant registers (only loaded during codegen entry) // 1. Constant registers (only loaded during codegen entry)
constexpr RegisterA64 rState = x19; // lua_State* L inline constexpr RegisterA64 rState = x19; // lua_State* L
constexpr RegisterA64 rNativeContext = x20; // NativeContext* context inline constexpr RegisterA64 rNativeContext = x20; // NativeContext* context
constexpr RegisterA64 rGlobalState = x21; // global_State* L->global inline constexpr RegisterA64 rGlobalState = x21; // global_State* L->global
// 2. Frame registers (reloaded when call frame changes; rBase is also reloaded after all calls that may reallocate stack) // 2. Frame registers (reloaded when call frame changes; rBase is also reloaded after all calls that may reallocate stack)
constexpr RegisterA64 rConstants = x22; // TValue* k inline constexpr RegisterA64 rConstants = x22; // TValue* k
constexpr RegisterA64 rClosure = x23; // Closure* cl inline constexpr RegisterA64 rClosure = x23; // Closure* cl
constexpr RegisterA64 rCode = x24; // Instruction* code inline constexpr RegisterA64 rCode = x24; // Instruction* code
constexpr RegisterA64 rBase = x25; // StkId base inline constexpr RegisterA64 rBase = x25; // StkId base
// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point
// See CodeGenA64.cpp for layout // See CodeGenA64.cpp for layout
constexpr unsigned kStashSlots = 9; // stashed non-volatile registers inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers
constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury!
constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers inline constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers
constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; inline constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8;
constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8); inline constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8);
constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8); inline constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8);
inline void emitUpdateBase(AssemblyBuilderA64& build) inline void emitUpdateBase(AssemblyBuilderA64& build)
{ {

View file

@ -33,22 +33,22 @@ namespace X64
struct IrRegAllocX64; struct IrRegAllocX64;
constexpr uint32_t kFunctionAlignment = 32; inline constexpr uint32_t kFunctionAlignment = 32;
// Data that is very common to access is placed in non-volatile registers // Data that is very common to access is placed in non-volatile registers
constexpr RegisterX64 rState = r15; // lua_State* L inline constexpr RegisterX64 rState = r15; // lua_State* L
constexpr RegisterX64 rBase = r14; // StkId base inline constexpr RegisterX64 rBase = r14; // StkId base
constexpr RegisterX64 rNativeContext = r13; // NativeContext* context inline constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
constexpr RegisterX64 rConstants = r12; // TValue* k inline constexpr RegisterX64 rConstants = r12; // TValue* k
constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below inline constexpr unsigned kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below
constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into inline constexpr unsigned kSpillSlots = 13; // Number of 8 byte slots available for register allocator to spill data into
static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment"); static_assert((kExtraLocals + kSpillSlots) * 8 % 16 == 0, "locals have to preserve 16 byte alignment");
constexpr uint8_t kWindowsFirstNonVolXmmReg = 6; inline constexpr uint8_t kWindowsFirstNonVolXmmReg = 6;
constexpr uint8_t kWindowsUsableXmmRegs = 10; // Some xmm regs are non-volatile, we have to balance how many we want to use/preserve inline constexpr uint8_t kWindowsUsableXmmRegs = 10; // Some xmm regs are non-volatile, we have to balance how many we want to use/preserve
constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile inline constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile
inline uint8_t getXmmRegisterCount(ABIX64 abi) inline uint8_t getXmmRegisterCount(ABIX64 abi)
{ {
@ -57,11 +57,11 @@ inline uint8_t getXmmRegisterCount(ABIX64 abi)
// Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point
// Stack is separated into sections for different data. See CodeGenX64.cpp for layout overview // Stack is separated into sections for different data. See CodeGenX64.cpp for layout overview
constexpr unsigned kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage inline constexpr unsigned kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage
constexpr unsigned kStackLocalStorage = 8 * kExtraLocals; inline constexpr unsigned kStackLocalStorage = 8 * kExtraLocals;
constexpr unsigned kStackSpillStorage = 8 * kSpillSlots; inline constexpr unsigned kStackSpillStorage = 8 * kSpillSlots;
constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used under Windows ABI inline constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used under Windows ABI
constexpr unsigned kStackRegHomeStorage = 4 * 8; // Register 'home' locations that can be used by callees under Windows ABI inline constexpr unsigned kStackRegHomeStorage = 4 * 8; // Register 'home' locations that can be used by callees under Windows ABI
inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount) inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount)
{ {
@ -77,19 +77,19 @@ inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount)
} }
// Useful offsets to specific parts // Useful offsets to specific parts
constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage; inline constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage;
constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage; inline constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage;
inline unsigned getFullStackSize(ABIX64 abi, uint8_t xmmRegCount) inline unsigned getFullStackSize(ABIX64 abi, uint8_t xmmRegCount)
{ {
return kStackOffsetToSpillSlots + kStackSpillStorage + getNonVolXmmStorageSize(abi, xmmRegCount) + kStackAlign; return kStackOffsetToSpillSlots + kStackSpillStorage + getNonVolXmmStorageSize(abi, xmmRegCount) + kStackAlign;
} }
constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl inline constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl
constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code inline constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16]; inline constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16];
constexpr OperandX64 sSpillArea = addr[rsp + kStackOffsetToSpillSlots]; inline constexpr OperandX64 sSpillArea = addr[rsp + kStackOffsetToSpillSlots];
inline OperandX64 luauReg(int ri) inline OperandX64 luauReg(int ri)
{ {

View file

@ -29,6 +29,7 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauCompileFixTypeFunctionSkip)
namespace Luau namespace Luau
{ {
@ -3933,6 +3934,11 @@ struct Compiler
return false; return false;
} }
bool visit(AstStatTypeFunction* node) override
{
return !FFlag::LuauCompileFixTypeFunctionSkip;
}
}; };
struct UndefinedLocalVisitor : AstVisitor struct UndefinedLocalVisitor : AstVisitor

View file

@ -85,7 +85,7 @@ struct LintOptions
}; };
// clang-format off // clang-format off
static const char* kWarningNames[] = { inline constexpr const char* kWarningNames[] = {
"Unknown", "Unknown",
"UnknownGlobal", "UnknownGlobal",

View file

@ -50,15 +50,15 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c
ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o) ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o)
ISOCLINE_TARGET=$(BUILD)/libisocline.a ISOCLINE_TARGET=$(BUILD)/libisocline.a
TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplRequirer.cpp CLI/src/RequirerUtils.cpp TESTS_SOURCES=$(wildcard tests/*.cpp) CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplRequirer.cpp CLI/src/VfsNavigator.cpp
TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o) TESTS_OBJECTS=$(TESTS_SOURCES:%=$(BUILD)/%.o)
TESTS_TARGET=$(BUILD)/luau-tests TESTS_TARGET=$(BUILD)/luau-tests
REPL_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/ReplRequirer.cpp CLI/src/RequirerUtils.cpp REPL_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Profiler.cpp CLI/src/Coverage.cpp CLI/src/Repl.cpp CLI/src/ReplEntry.cpp CLI/src/ReplRequirer.cpp CLI/src/VfsNavigator.cpp
REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o) REPL_CLI_OBJECTS=$(REPL_CLI_SOURCES:%=$(BUILD)/%.o)
REPL_CLI_TARGET=$(BUILD)/luau REPL_CLI_TARGET=$(BUILD)/luau
ANALYZE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Analyze.cpp CLI/src/AnalyzeRequirer.cpp CLI/src/RequirerUtils.cpp ANALYZE_CLI_SOURCES=CLI/src/FileUtils.cpp CLI/src/Flags.cpp CLI/src/Analyze.cpp CLI/src/AnalyzeRequirer.cpp CLI/src/VfsNavigator.cpp
ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o) ANALYZE_CLI_OBJECTS=$(ANALYZE_CLI_SOURCES:%=$(BUILD)/%.o)
ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze ANALYZE_CLI_TARGET=$(BUILD)/luau-analyze

View file

@ -59,7 +59,18 @@ public:
virtual NavigateResult toParent() = 0; virtual NavigateResult toParent() = 0;
virtual NavigateResult toChild(const std::string& component) = 0; virtual NavigateResult toChild(const std::string& component) = 0;
enum class ConfigBehavior
{
GetAlias,
GetConfig
};
virtual bool isConfigPresent() const = 0; virtual bool isConfigPresent() const = 0;
// The result of getConfigBehavior determines whether getAlias or getConfig
// is called when isConfigPresent returns true.
virtual ConfigBehavior getConfigBehavior() const = 0;
virtual std::optional<std::string> getAlias(const std::string& alias) const = 0;
virtual std::optional<std::string> getConfig() const = 0; virtual std::optional<std::string> getConfig() const = 0;
}; };
@ -94,7 +105,8 @@ private:
NavigationContext& navigationContext; NavigationContext& navigationContext;
ErrorHandler& errorHandler; ErrorHandler& errorHandler;
Luau::Config config;
std::optional<std::string> foundAliasValue;
}; };
} // namespace Luau::Require } // namespace Luau::Require

View file

@ -78,7 +78,7 @@ Error Navigator::navigateImpl(std::string_view path)
if (Error error = navigateToAndPopulateConfig(alias)) if (Error error = navigateToAndPopulateConfig(alias))
return error; return error;
if (!config.aliases.contains(alias)) if (!foundAliasValue)
{ {
if (alias != "self") if (alias != "self")
return "@" + alias + " is not a valid alias"; return "@" + alias + " is not a valid alias";
@ -93,7 +93,7 @@ Error Navigator::navigateImpl(std::string_view path)
return std::nullopt; return std::nullopt;
} }
if (Error error = navigateToAlias(alias, config.aliases[alias].value)) if (Error error = navigateToAlias(alias, *foundAliasValue))
return error; return error;
if (Error error = navigateThroughPath(path)) if (Error error = navigateThroughPath(path))
return error; return error;
@ -169,12 +169,20 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va
Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
{ {
while (!config.aliases.contains(desiredAlias)) Luau::Config config;
while (!foundAliasValue)
{ {
if (navigationContext.toParent() != NavigationContext::NavigateResult::Success) if (navigationContext.toParent() != NavigationContext::NavigateResult::Success)
break; break;
if (navigationContext.isConfigPresent()) if (navigationContext.isConfigPresent())
{
if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias)
{
foundAliasValue = navigationContext.getAlias(desiredAlias);
}
else
{ {
std::optional<std::string> configContents = navigationContext.getConfig(); std::optional<std::string> configContents = navigationContext.getConfig();
if (!configContents) if (!configContents)
@ -188,6 +196,10 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
if (Error error = Luau::parseConfig(*configContents, config, opts)) if (Error error = Luau::parseConfig(*configContents, config, opts))
return error; return error;
if (config.aliases.contains(desiredAlias))
foundAliasValue = config.aliases[desiredAlias].value;
}
} }
}; };

View file

@ -101,8 +101,18 @@ struct luarequire_Configuration
// configuration file is present or NAVIGATE_FAILURE is returned (at root). // configuration file is present or NAVIGATE_FAILURE is returned (at root).
bool (*is_config_present)(lua_State* L, void* ctx); bool (*is_config_present)(lua_State* L, void* ctx);
// Parses the configuration file in the current context for the given alias
// and returns its value or WRITE_FAILURE if not found. This function is
// only called if is_config_present returns true. If this function pointer
// is set, get_config must not be set. Opting in to this function pointer
// disables parsing configuration files internally and can be used for finer
// control over the configuration file parsing process.
luarequire_WriteResult (*get_alias)(lua_State* L, void* ctx, const char* alias, char* buffer, size_t buffer_size, size_t* size_out);
// Provides the contents of the configuration file in the current context. // Provides the contents of the configuration file in the current context.
// This function is only called if is_config_present returns true. // This function is only called if is_config_present returns true. If this
// function pointer is set, get_alias must not be set. Opting in to this
// function pointer enables parsing configuration files internally.
luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out);
// Executes the module and places the result on the stack. Returns the // Executes the module and places the result on the stack. Returns the

View file

@ -82,6 +82,18 @@ bool RuntimeNavigationContext::isConfigPresent() const
return config->is_config_present(L, ctx); return config->is_config_present(L, ctx);
} }
NavigationContext::ConfigBehavior RuntimeNavigationContext::getConfigBehavior() const
{
if (config->get_alias)
return ConfigBehavior::GetAlias;
return ConfigBehavior::GetConfig;
}
std::optional<std::string> RuntimeNavigationContext::getAlias(const std::string& alias) const
{
return getStringFromCWriterWithInput(config->get_alias, alias, initalIdentifierBufferSize);
}
std::optional<std::string> RuntimeNavigationContext::getConfig() const std::optional<std::string> RuntimeNavigationContext::getConfig() const
{ {
return getStringFromCWriter(config->get_config, initalFileBufferSize); return getStringFromCWriter(config->get_config, initalFileBufferSize);
@ -112,17 +124,45 @@ std::optional<std::string> RuntimeNavigationContext::getStringFromCWriter(
return std::nullopt; return std::nullopt;
} }
std::optional<std::string> RuntimeNavigationContext::getStringFromCWriterWithInput(
luarequire_WriteResult (*writer)(lua_State* L, void* ctx, const char* input, char* buffer, size_t buffer_size, size_t* size_out),
std::string input,
size_t initalBufferSize
) const
{
std::string buffer;
buffer.resize(initalBufferSize);
RuntimeErrorHandler::RuntimeErrorHandler(lua_State* L, std::string requiredPath) size_t size;
: L(L) luarequire_WriteResult result = writer(L, ctx, input.c_str(), buffer.data(), buffer.size(), &size);
, errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ") if (result == WRITE_BUFFER_TOO_SMALL)
{
buffer.resize(size);
result = writer(L, ctx, input.c_str(), buffer.data(), buffer.size(), &size);
}
if (result == WRITE_SUCCESS)
{
buffer.resize(size);
return buffer;
}
return std::nullopt;
}
RuntimeErrorHandler::RuntimeErrorHandler(std::string requiredPath)
: errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ")
{ {
} }
void RuntimeErrorHandler::reportError(std::string message) void RuntimeErrorHandler::reportError(std::string message)
{ {
std::string fullError = errorPrefix + std::move(message); errorMessage = errorPrefix + std::move(message);
luaL_errorL(L, "%s", fullError.c_str()); }
const std::string& RuntimeErrorHandler::getReportedError() const
{
return errorMessage;
} }
} // namespace Luau::Require } // namespace Luau::Require

View file

@ -27,6 +27,8 @@ public:
NavigateResult toChild(const std::string& component) override; NavigateResult toChild(const std::string& component) override;
bool isConfigPresent() const override; bool isConfigPresent() const override;
NavigationContext::ConfigBehavior getConfigBehavior() const override;
std::optional<std::string> getAlias(const std::string& alias) const override;
std::optional<std::string> getConfig() const override; std::optional<std::string> getConfig() const override;
// Custom capabilities // Custom capabilities
@ -40,6 +42,11 @@ private:
luarequire_WriteResult (*writer)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out), luarequire_WriteResult (*writer)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out),
size_t initalBufferSize size_t initalBufferSize
) const; ) const;
std::optional<std::string> getStringFromCWriterWithInput(
luarequire_WriteResult (*writer)(lua_State* L, void* ctx, const char* input, char* buffer, size_t buffer_size, size_t* size_out),
std::string input,
size_t initalBufferSize
) const;
luarequire_Configuration* config; luarequire_Configuration* config;
lua_State* L; lua_State* L;
@ -47,15 +54,18 @@ private:
std::string requirerChunkname; std::string requirerChunkname;
}; };
// Non-throwing error reporter
class RuntimeErrorHandler : public ErrorHandler class RuntimeErrorHandler : public ErrorHandler
{ {
public: public:
RuntimeErrorHandler(lua_State* L, std::string requiredPath); RuntimeErrorHandler(std::string requiredPath);
void reportError(std::string message) override; void reportError(std::string message) override;
const std::string& getReportedError() const;
private: private:
lua_State* L;
std::string errorPrefix; std::string errorPrefix;
std::string errorMessage;
}; };
} // namespace Luau::Require } // namespace Luau::Require

View file

@ -29,8 +29,10 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config)
luaL_error(L, "require configuration is missing required function pointer: get_cache_key"); luaL_error(L, "require configuration is missing required function pointer: get_cache_key");
if (!config.is_config_present) if (!config.is_config_present)
luaL_error(L, "require configuration is missing required function pointer: is_config_present"); luaL_error(L, "require configuration is missing required function pointer: is_config_present");
if (!config.get_config) if (config.get_alias && config.get_config)
luaL_error(L, "require configuration is missing required function pointer: get_config"); luaL_error(L, "require configuration cannot define both get_alias and get_config");
if (!config.get_alias && !config.get_config)
luaL_error(L, "require configuration is missing required function pointer: either get_alias or get_config (not both)");
if (!config.load) if (!config.load)
luaL_error(L, "require configuration is missing required function pointer: load"); luaL_error(L, "require configuration is missing required function pointer: load");
} }

View file

@ -21,6 +21,16 @@ static const char* requiredCacheTableKey = "_MODULES";
struct ResolvedRequire struct ResolvedRequire
{ {
static ResolvedRequire fromErrorHandler(const RuntimeErrorHandler& errorHandler)
{
return {ResolvedRequire::Status::ErrorReported, "", "", "", errorHandler.getReportedError()};
}
static ResolvedRequire fromErrorMessage(const char* message)
{
return {ResolvedRequire::Status::ErrorReported, "", "", "", message};
}
enum class Status enum class Status
{ {
Cached, Cached,
@ -32,6 +42,7 @@ struct ResolvedRequire
std::string chunkname; std::string chunkname;
std::string loadname; std::string loadname;
std::string cacheKey; std::string cacheKey;
std::string error;
}; };
static bool isCached(lua_State* L, const std::string& key) static bool isCached(lua_State* L, const std::string& key)
@ -47,30 +58,24 @@ static bool isCached(lua_State* L, const std::string& key)
static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path) static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path)
{ {
if (!lrc->is_require_allowed(L, ctx, requirerChunkname)) if (!lrc->is_require_allowed(L, ctx, requirerChunkname))
luaL_error(L, "require is not supported in this context"); return ResolvedRequire::fromErrorMessage("require is not supported in this context");
RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname}; RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname};
RuntimeErrorHandler errorHandler{L, path}; // Errors reported directly to lua_State. RuntimeErrorHandler errorHandler{path};
Navigator navigator(navigationContext, errorHandler); Navigator navigator(navigationContext, errorHandler);
// Updates navigationContext while navigating through the given path. // Updates navigationContext while navigating through the given path.
Navigator::Status status = navigator.navigate(std::move(path)); Navigator::Status status = navigator.navigate(std::move(path));
if (status == Navigator::Status::ErrorReported) if (status == Navigator::Status::ErrorReported)
return {ResolvedRequire::Status::ErrorReported}; return ResolvedRequire::fromErrorHandler(errorHandler);
if (!navigationContext.isModulePresent()) if (!navigationContext.isModulePresent())
{ return ResolvedRequire::fromErrorMessage("no module present at resolved path");
errorHandler.reportError("no module present at resolved path");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
std::optional<std::string> cacheKey = navigationContext.getCacheKey(); std::optional<std::string> cacheKey = navigationContext.getCacheKey();
if (!cacheKey) if (!cacheKey)
{ return ResolvedRequire::fromErrorMessage("could not get cache key for module");
errorHandler.reportError("could not get cache key for module");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
if (isCached(L, *cacheKey)) if (isCached(L, *cacheKey))
{ {
@ -84,17 +89,11 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State*
std::optional<std::string> chunkname = navigationContext.getChunkname(); std::optional<std::string> chunkname = navigationContext.getChunkname();
if (!chunkname) if (!chunkname)
{ return ResolvedRequire::fromErrorMessage("could not get chunkname for module");
errorHandler.reportError("could not get chunkname for module");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
std::optional<std::string> loadname = navigationContext.getLoadname(); std::optional<std::string> loadname = navigationContext.getLoadname();
if (!loadname) if (!loadname)
{ return ResolvedRequire::fromErrorMessage("could not get loadname for module");
errorHandler.reportError("could not get loadname for module");
return ResolvedRequire{ResolvedRequire::Status::ErrorReported};
}
return ResolvedRequire{ return ResolvedRequire{
ResolvedRequire::Status::ModuleRead, ResolvedRequire::Status::ModuleRead,
@ -118,12 +117,14 @@ static int checkRegisteredModules(lua_State* L, const char* path)
return 1; return 1;
} }
static const int kRequireStackValues = 4;
int lua_requirecont(lua_State* L, int status) int lua_requirecont(lua_State* L, int status)
{ {
// Number of stack arguments present before this continuation is called. // Number of stack arguments present before this continuation is called.
const int numStackArgs = lua_tointeger(L, 1); LUAU_ASSERT(lua_gettop(L) >= kRequireStackValues);
const int numResults = lua_gettop(L) - numStackArgs; const int numResults = lua_gettop(L) - kRequireStackValues;
const char* cacheKey = luaL_checkstring(L, numStackArgs); const char* cacheKey = luaL_checkstring(L, 2);
if (numResults > 1) if (numResults > 1)
luaL_error(L, "module must return a single value"); luaL_error(L, "module must return a single value");
@ -152,10 +153,8 @@ int lua_requirecont(lua_State* L, int status)
int lua_requireinternal(lua_State* L, const char* requirerChunkname) int lua_requireinternal(lua_State* L, const char* requirerChunkname)
{ {
int stackTop = lua_gettop(L); // Discard extra arguments, we only use path
lua_settop(L, 1);
// If modifying the state of the stack, please update numStackArgs in the
// lua_requirecont continuation function.
luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1))); luarequire_Configuration* lrc = static_cast<luarequire_Configuration*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!lrc) if (!lrc)
@ -169,22 +168,42 @@ int lua_requireinternal(lua_State* L, const char* requirerChunkname)
if (checkRegisteredModules(L, path) == 1) if (checkRegisteredModules(L, path) == 1)
return 1; return 1;
// ResolvedRequire will be destroyed and any string will be pinned to Luau stack, so that luaL_error doesn't need destructors
bool resolveError = false;
{
ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
if (resolvedRequire.status == ResolvedRequire::Status::Cached) if (resolvedRequire.status == ResolvedRequire::Status::Cached)
return 1; return 1;
// (1) path, ..., cacheKey if (resolvedRequire.status == ResolvedRequire::Status::ErrorReported)
{
lua_pushstring(L, resolvedRequire.error.c_str());
resolveError = true;
}
else
{
// (1) path, ..., cacheKey, chunkname, loadname
lua_pushstring(L, resolvedRequire.cacheKey.c_str()); lua_pushstring(L, resolvedRequire.cacheKey.c_str());
lua_pushstring(L, resolvedRequire.chunkname.c_str());
lua_pushstring(L, resolvedRequire.loadname.c_str());
}
}
// Insert number of arguments before the continuation to check the results. if (resolveError)
int numArgsBeforeLoad = stackTop + 2; lua_error(L); // Error already on top of the stack
lua_pushinteger(L, numArgsBeforeLoad);
lua_insert(L, 1);
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.loadname.c_str()); int stackValues = lua_gettop(L);
LUAU_ASSERT(stackValues == kRequireStackValues);
const char* chunkname = lua_tostring(L, -2);
const char* loadname = lua_tostring(L, -1);
int numResults = lrc->load(L, ctx, path, chunkname, loadname);
if (numResults == -1) if (numResults == -1)
{ {
if (lua_gettop(L) != numArgsBeforeLoad) if (lua_gettop(L) != stackValues)
luaL_error(L, "stack cannot be modified when require yields"); luaL_error(L, "stack cannot be modified when require yields");
return lua_yield(L, 0); return lua_yield(L, 0);

View file

@ -398,9 +398,11 @@ target_sources(isocline PRIVATE
target_sources(Luau.CLI.lib PRIVATE target_sources(Luau.CLI.lib PRIVATE
CLI/include/Luau/FileUtils.h CLI/include/Luau/FileUtils.h
CLI/include/Luau/Flags.h CLI/include/Luau/Flags.h
CLI/include/Luau/VfsNavigator.h
CLI/src/FileUtils.cpp CLI/src/FileUtils.cpp
CLI/src/Flags.cpp CLI/src/Flags.cpp
CLI/src/VfsNavigator.cpp
) )
if(TARGET Luau.Repl.CLI) if(TARGET Luau.Repl.CLI)
@ -409,14 +411,12 @@ if(TARGET Luau.Repl.CLI)
CLI/include/Luau/Coverage.h CLI/include/Luau/Coverage.h
CLI/include/Luau/Profiler.h CLI/include/Luau/Profiler.h
CLI/include/Luau/ReplRequirer.h CLI/include/Luau/ReplRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Coverage.cpp CLI/src/Coverage.cpp
CLI/src/Profiler.cpp CLI/src/Profiler.cpp
CLI/src/Repl.cpp CLI/src/Repl.cpp
CLI/src/ReplEntry.cpp CLI/src/ReplEntry.cpp
CLI/src/ReplRequirer.cpp CLI/src/ReplRequirer.cpp
CLI/src/RequirerUtils.cpp
) )
endif() endif()
@ -424,11 +424,9 @@ if(TARGET Luau.Analyze.CLI)
# Luau.Analyze.CLI Sources # Luau.Analyze.CLI Sources
target_sources(Luau.Analyze.CLI PRIVATE target_sources(Luau.Analyze.CLI PRIVATE
CLI/include/Luau/AnalyzeRequirer.h CLI/include/Luau/AnalyzeRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Analyze.cpp CLI/src/Analyze.cpp
CLI/src/AnalyzeRequirer.cpp CLI/src/AnalyzeRequirer.cpp
CLI/src/RequirerUtils.cpp
) )
endif() endif()
@ -563,13 +561,11 @@ if(TARGET Luau.CLI.Test)
CLI/include/Luau/Coverage.h CLI/include/Luau/Coverage.h
CLI/include/Luau/Profiler.h CLI/include/Luau/Profiler.h
CLI/include/Luau/ReplRequirer.h CLI/include/Luau/ReplRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Coverage.cpp CLI/src/Coverage.cpp
CLI/src/Profiler.cpp CLI/src/Profiler.cpp
CLI/src/Repl.cpp CLI/src/Repl.cpp
CLI/src/ReplRequirer.cpp CLI/src/ReplRequirer.cpp
CLI/src/RequirerUtils.cpp
tests/RegisterCallbacks.h tests/RegisterCallbacks.h
tests/RegisterCallbacks.cpp tests/RegisterCallbacks.cpp

View file

@ -329,8 +329,6 @@ LUA_API lua_Destructor lua_getuserdatadtor(lua_State* L, int tag);
LUA_API void lua_setuserdatametatable(lua_State* L, int tag); LUA_API void lua_setuserdatametatable(lua_State* L, int tag);
LUA_API void lua_getuserdatametatable(lua_State* L, int tag); LUA_API void lua_getuserdatametatable(lua_State* L, int tag);
LUA_API void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx); // Deprecated for incorrect behavior with 'idx != -1'
LUA_API void lua_setlightuserdataname(lua_State* L, int tag, const char* name); LUA_API void lua_setlightuserdataname(lua_State* L, int tag, const char* name);
LUA_API const char* lua_getlightuserdataname(lua_State* L, int tag); LUA_API const char* lua_getlightuserdataname(lua_State* L, int tag);

View file

@ -128,21 +128,6 @@
// }================================================================== // }==================================================================
/*
@@ LUAI_USER_ALIGNMENT_T is a type that requires maximum alignment.
** CHANGE it if your system requires alignments larger than double. (For
** instance, if your system supports long doubles and they must be
** aligned in 16-byte boundaries, then you should add long double in the
** union.) Probably you do not need to change this.
*/
#define LUAI_USER_ALIGNMENT_T \
union \
{ \
double u; \
void* s; \
long l; \
}
#ifndef LUA_VECTOR_SIZE #ifndef LUA_VECTOR_SIZE
#define LUA_VECTOR_SIZE 3 // must be 3 or 4 #define LUA_VECTOR_SIZE 3 // must be 3 or 4
#endif #endif

View file

@ -1479,16 +1479,6 @@ void lua_setuserdatametatable(lua_State* L, int tag)
L->top--; L->top--;
} }
void lua_setuserdatametatable_DEPRECATED(lua_State* L, int tag, int idx)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);
api_check(L, !L->global->udatamt[tag]); // reassignment not supported
StkId o = index2addr(L, idx);
api_check(L, ttistable(o));
L->global->udatamt[tag] = hvalue(o);
L->top--;
}
void lua_getuserdatametatable(lua_State* L, int tag) void lua_getuserdatametatable(lua_State* L, int tag)
{ {
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);

View file

@ -9,8 +9,6 @@
#include "Luau/Common.h" #include "Luau/Common.h"
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
// internal assertions for in-house debugging // internal assertions for in-house debugging
#define check_exp(c, e) (LUAU_ASSERT(c), (e)) #define check_exp(c, e) (LUAU_ASSERT(c), (e))
#define api_check(l, e) LUAU_ASSERT(e) #define api_check(l, e) LUAU_ASSERT(e)

View file

@ -12,11 +12,16 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauCurrentLineBounds)
static const char* getfuncname(Closure* f); static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci) static int currentpc(lua_State* L, CallInfo* ci)
{ {
if (FFlag::LuauCurrentLineBounds)
return pcRel(ci->savedpc, ci_func(ci)->l.p); return pcRel(ci->savedpc, ci_func(ci)->l.p);
else
return pcRel_DEPRECATED(ci->savedpc, ci_func(ci)->l.p);
} }
static int currentline(lua_State* L, CallInfo* ci) static int currentline(lua_State* L, CallInfo* ci)

View file

@ -4,7 +4,9 @@
#include "lstate.h" #include "lstate.h"
#define pcRel(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0) #define pcRel(pc, p) ((pc) && (pc) != (p)->code ? cast_to(int, (pc) - (p)->code) - 1 : 0)
// TODO: remove with FFlagLuauCurrentLineBounds
#define pcRel_DEPRECATED(pc, p) ((pc) ? cast_to(int, (pc) - (p)->code) - 1 : 0)
#define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname) #define luaG_typeerror(L, o, opname) luaG_typeerrorL(L, o, opname)
#define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what) #define luaG_forerror(L, o, what) luaG_forerrorL(L, o, what)

View file

@ -14,6 +14,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
static void validateobjref(global_State* g, GCObject* f, GCObject* t) static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{ {
LUAU_ASSERT(!isdead(g, t)); LUAU_ASSERT(!isdead(g, t));
@ -462,7 +464,7 @@ static void dumpthread(FILE* f, lua_State* th)
else if (isLua(ci)) else if (isLua(ci))
{ {
Proto* p = ci_func(ci)->l.p; Proto* p = ci_func(ci)->l.p;
int pc = pcRel(ci->savedpc, p); int pc = FFlag::LuauCurrentLineBounds ? pcRel(ci->savedpc, p) : pcRel_DEPRECATED(ci->savedpc, p);
const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc); const LocVar* var = luaF_findlocal(p, int(v - ci->base), pc);
if (var && var->varname) if (var && var->varname)

View file

@ -96,7 +96,7 @@
#endif #endif
/* /*
* The sizes of Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency * The sizes of most Luau objects aren't crucial for code correctness, but they are crucial for memory efficiency
* To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock * To prevent some of them accidentally growing and us losing memory without realizing it, we're going to lock
* the sizes of all critical structures down. * the sizes of all critical structures down.
*/ */
@ -120,10 +120,12 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table
#endif #endif
static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header");
static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header");
static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header"); static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header");
static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header"); static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header");
// The userdata is designed to provide 16 byte alignment for 16 byte and larger userdata sizes
static_assert(offsetof(Udata, data) == 16, "data must be at precise offset provide proper alignment");
const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kSizeClasses = LUA_SIZECLASSES;
// Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size // Controls the number of entries in SizeClassConfig and define the maximum possible paged allocation size
@ -221,14 +223,15 @@ struct lua_Page
int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead int freeNext; // next free block offset in this page, in bytes; when negative, freeList is used instead
int busyBlocks; // number of blocks allocated out of this page int busyBlocks; // number of blocks allocated out of this page
union // provide additional padding based on current object size to provide 16 byte alignment of data
{ // later static_assert checks that this requirement is held
char padding[sizeof(void*) == 8 ? 8 : 12];
char data[1]; char data[1];
double align1;
void* align2;
};
}; };
static_assert(offsetof(lua_Page, data) % 16 == 0, "data must be 16 byte aligned to provide properly aligned allocation of userdata objects");
l_noret luaM_toobig(lua_State* L) l_noret luaM_toobig(lua_State* L)
{ {
luaG_runerror(L, "memory allocation error: block too big"); luaG_runerror(L, "memory allocation error: block too big");

View file

@ -265,11 +265,9 @@ typedef struct Udata
struct LuaTable* metatable; struct LuaTable* metatable;
union // userdata is allocated right after the header
{ // while the alignment is only 8 here, for sizes starting at 16 bytes, 16 byte alignment is provided
char data[1]; // userdata is allocated right after the header alignas(8) char data[1];
L_Umaxalign dummy; // ensures maximum alignment for data
};
} Udata; } Udata;
typedef struct LuauBuffer typedef struct LuauBuffer
@ -278,11 +276,7 @@ typedef struct LuauBuffer
unsigned int len; unsigned int len;
union alignas(8) char data[1];
{
char data[1]; // buffer is allocated right after the header
L_Umaxalign dummy; // ensures maximum alignment for data
};
} Buffer; } Buffer;
/* /*

View file

@ -366,7 +366,7 @@ static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key)
int k; int k;
double n = nvalue(key); double n = nvalue(key);
luai_num2int(k, n); luai_num2int(k, n);
if (luai_numeq(cast_num(k), n) && cast_to(unsigned int, k - 1) < cast_to(unsigned int, t->sizearray)) if (luai_numeq(cast_num(k), n) && unsigned(k) - 1 < unsigned(t->sizearray))
return &t->array[k - 1]; return &t->array[k - 1];
} }
@ -604,7 +604,7 @@ static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key)
const TValue* luaH_getnum(LuaTable* t, int key) const TValue* luaH_getnum(LuaTable* t, int key)
{ {
// (1 <= key && key <= t->sizearray) // (1 <= key && key <= t->sizearray)
if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) if (unsigned(key) - 1 < unsigned(t->sizearray))
return &t->array[key - 1]; return &t->array[key - 1];
else if (t->node != dummynode) else if (t->node != dummynode)
{ {
@ -701,7 +701,7 @@ TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key)
TValue* luaH_setnum(lua_State* L, LuaTable* t, int key) TValue* luaH_setnum(lua_State* L, LuaTable* t, int key)
{ {
// (1 <= key && key <= t->sizearray) // (1 <= key && key <= t->sizearray)
if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) if (unsigned(key) - 1 < unsigned(t->sizearray))
return &t->array[key - 1]; return &t->array[key - 1];
// hash fallback // hash fallback
const TValue* p = luaH_getnum(t, key); const TValue* p = luaH_getnum(t, key);

View file

@ -95,10 +95,8 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t)
int n = e - f + 1; // number of elements to move int n = e - f + 1; // number of elements to move
if (cast_to(unsigned int, f - 1) < cast_to(unsigned int, src->sizearray) && if (unsigned(f) - 1 < unsigned(src->sizearray) && unsigned(t) - 1 < unsigned(dst->sizearray) &&
cast_to(unsigned int, t - 1) < cast_to(unsigned int, dst->sizearray) && unsigned(f) - 1 + unsigned(n) <= unsigned(src->sizearray) && unsigned(t) - 1 + unsigned(n) <= unsigned(dst->sizearray))
cast_to(unsigned int, f - 1 + n) <= cast_to(unsigned int, src->sizearray) &&
cast_to(unsigned int, t - 1 + n) <= cast_to(unsigned int, dst->sizearray))
{ {
TValue* srcarray = src->array; TValue* srcarray = src->array;
TValue* dstarray = dst->array; TValue* dstarray = dst->array;

View file

@ -10,7 +10,8 @@
// special tag value is used for newproxy-created user data (all other user data objects are host-exposed) // special tag value is used for newproxy-created user data (all other user data objects are host-exposed)
#define UTAG_PROXY (LUA_UTAG_LIMIT + 1) #define UTAG_PROXY (LUA_UTAG_LIMIT + 1)
#define sizeudata(len) (offsetof(Udata, data) + len) // userdata larger than 16 bytes will be extended to guarantee 16 byte alignment of subsequent blocks
#define sizeudata(len) (offsetof(Udata, data) + (len > 16 ? ((len + 15) & ~15) : len))
LUAI_FUNC Udata* luaU_newudata(lua_State* L, size_t s, int tag); LUAI_FUNC Udata* luaU_newudata(lua_State* L, size_t s, int tag);
LUAI_FUNC void luaU_freeudata(lua_State* L, Udata* u, struct lua_Page* page); LUAI_FUNC void luaU_freeudata(lua_State* L, Udata* u, struct lua_Page* page);

View file

@ -16,6 +16,8 @@
#include <string.h> #include <string.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
// Disable c99-designator to avoid the warning in CGOTO dispatch table // Disable c99-designator to avoid the warning in CGOTO dispatch table
#ifdef __clang__ #ifdef __clang__
#if __has_warning("-Wc99-designator") #if __has_warning("-Wc99-designator")
@ -147,6 +149,32 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
L->base = L->ci->base; L->base = L->ci->base;
} }
if (FFlag::LuauCurrentLineBounds)
{
Closure* cl = clvalue(L->ci->func);
// note: the pc expectations of the hook are matching the general "pc points to next instruction"
// however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
// this needs to be called before luaD_checkstack in case it fails to reallocate stack
const Instruction* oldsavedpc = L->ci->savedpc;
if (L->ci->savedpc && L->ci->savedpc != cl->l.p->code + cl->l.p->sizecode)
L->ci->savedpc++;
luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size
L->ci->top = L->top + LUA_MINSTACK;
LUAU_ASSERT(L->ci->top <= L->stack_last);
lua_Debug ar;
ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p));
ar.userdata = userdata;
hook(L, &ar);
L->ci->savedpc = oldsavedpc;
}
else
{
// note: the pc expectations of the hook are matching the general "pc points to next instruction" // note: the pc expectations of the hook are matching the general "pc points to next instruction"
// however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction // however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
// this needs to be called before luaD_checkstack in case it fails to reallocate stack // this needs to be called before luaD_checkstack in case it fails to reallocate stack
@ -167,6 +195,7 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
if (L->ci->savedpc) if (L->ci->savedpc)
L->ci->savedpc--; L->ci->savedpc--;
}
L->ci->top = restorestack(L, ci_top); L->ci->top = restorestack(L, ci_top);
L->top = restorestack(L, top); L->top = restorestack(L, top);
@ -646,7 +675,7 @@ reentry:
int index = int(indexd); int index = int(indexd);
// index has to be an exact integer and in-bounds for the array portion // index has to be an exact integer and in-bounds for the array portion
if (LUAU_LIKELY(unsigned(index - 1) < unsigned(h->sizearray) && !h->metatable && double(index) == indexd)) if (LUAU_LIKELY(unsigned(index) - 1 < unsigned(h->sizearray) && !h->metatable && double(index) == indexd))
{ {
setobj2s(L, ra, &h->array[unsigned(index - 1)]); setobj2s(L, ra, &h->array[unsigned(index - 1)]);
VM_NEXT(); VM_NEXT();
@ -676,7 +705,7 @@ reentry:
int index = int(indexd); int index = int(indexd);
// index has to be an exact integer and in-bounds for the array portion // index has to be an exact integer and in-bounds for the array portion
if (LUAU_LIKELY(unsigned(index - 1) < unsigned(h->sizearray) && !h->metatable && !h->readonly && double(index) == indexd)) if (LUAU_LIKELY(unsigned(index) - 1 < unsigned(h->sizearray) && !h->metatable && !h->readonly && double(index) == indexd))
{ {
setobj2t(L, &h->array[unsigned(index - 1)], ra); setobj2t(L, &h->array[unsigned(index - 1)], ra);
luaC_barriert(L, h, ra); luaC_barriert(L, h, ra);

View file

@ -13,7 +13,8 @@
#include <string.h> #include <string.h>
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens LUAU_FASTFLAGVARIABLE(LuauLoadNoOomThrow)
template<typename T> template<typename T>
struct TempBuffer struct TempBuffer
{ {
@ -21,11 +22,20 @@ struct TempBuffer
T* data; T* data;
size_t count; size_t count;
TempBuffer()
: L(NULL)
, data(NULL)
, count(0)
{
LUAU_ASSERT(FFlag::LuauLoadNoOomThrow);
}
TempBuffer(lua_State* L, size_t count) TempBuffer(lua_State* L, size_t count)
: L(L) : L(L)
, data(luaM_newarray(L, count, T, 0)) , data(luaM_newarray(L, count, T, 0))
, count(count) , count(count)
{ {
LUAU_ASSERT(!FFlag::LuauLoadNoOomThrow);
} }
TempBuffer(const TempBuffer&) = delete; TempBuffer(const TempBuffer&) = delete;
@ -36,9 +46,18 @@ struct TempBuffer
~TempBuffer() noexcept ~TempBuffer() noexcept
{ {
if (data)
luaM_freearray(L, data, count, T, 0); luaM_freearray(L, data, count, T, 0);
} }
void allocate(lua_State* L, size_t count)
{
LUAU_ASSERT(this->L == nullptr);
this->L = L;
this->data = luaM_newarray(L, count, T, 0);
this->count = count;
}
T& operator[](size_t index) T& operator[](size_t index)
{ {
LUAU_ASSERT(index < count); LUAU_ASSERT(index < count);
@ -242,7 +261,360 @@ static void remapUserdataTypes(char* data, size_t size, uint8_t* userdataRemappi
LUAU_ASSERT(offset == size); LUAU_ASSERT(offset == size);
} }
int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env) static int loadsafe(
lua_State* L,
TempBuffer<TString*>& strings,
TempBuffer<Proto*>& protos,
const char* chunkname,
const char* data,
size_t size,
int env
)
{
size_t offset = 0;
uint8_t version = read<uint8_t>(data, size, offset);
// 0 means the rest of the bytecode is the error message
if (version == 0)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(L, "%s%.*s", chunkid, int(size - offset), data + offset);
return 1;
}
if (version < LBC_VERSION_MIN || version > LBC_VERSION_MAX)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(L, "%s: bytecode version mismatch (expected [%d..%d], got %d)", chunkid, LBC_VERSION_MIN, LBC_VERSION_MAX, version);
return 1;
}
uint8_t typesversion = 0;
if (version >= 4)
{
typesversion = read<uint8_t>(data, size, offset);
if (typesversion < LBC_TYPE_VERSION_MIN || typesversion > LBC_TYPE_VERSION_MAX)
{
char chunkbuf[LUA_IDSIZE];
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
lua_pushfstring(
L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN, LBC_TYPE_VERSION_MAX, typesversion
);
return 1;
}
}
// env is 0 for current environment and a stack index otherwise
LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env));
TString* source = luaS_new(L, chunkname);
// string table
unsigned int stringCount = readVarInt(data, size, offset);
strings.allocate(L, stringCount);
for (unsigned int i = 0; i < stringCount; ++i)
{
unsigned int length = readVarInt(data, size, offset);
strings[i] = luaS_newlstr(L, data + offset, length);
offset += length;
}
// userdata type remapping table
// for unknown userdata types, the entry will remap to common 'userdata' type
const uint32_t userdataTypeLimit = LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE;
uint8_t userdataRemapping[userdataTypeLimit];
if (typesversion == 3)
{
memset(userdataRemapping, LBC_TYPE_USERDATA, userdataTypeLimit);
uint8_t index = read<uint8_t>(data, size, offset);
while (index != 0)
{
TString* name = readString(strings, data, size, offset);
if (uint32_t(index - 1) < userdataTypeLimit)
{
if (auto cb = L->global->ecb.gettypemapping)
userdataRemapping[index - 1] = cb(L, getstr(name), name->len);
}
index = read<uint8_t>(data, size, offset);
}
}
// proto table
unsigned int protoCount = readVarInt(data, size, offset);
protos.allocate(L, protoCount);
for (unsigned int i = 0; i < protoCount; ++i)
{
Proto* p = luaF_newproto(L);
p->source = source;
p->bytecodeid = int(i);
p->maxstacksize = read<uint8_t>(data, size, offset);
p->numparams = read<uint8_t>(data, size, offset);
p->nups = read<uint8_t>(data, size, offset);
p->is_vararg = read<uint8_t>(data, size, offset);
if (version >= 4)
{
p->flags = read<uint8_t>(data, size, offset);
if (typesversion == 1)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
LUAU_ASSERT(typesize == unsigned(2 + p->numparams));
LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION);
LUAU_ASSERT(types[1] == p->numparams);
// transform v1 into v2 format
int headersize = typesize > 127 ? 4 : 3;
p->typeinfo = luaM_newarray(L, headersize + typesize, uint8_t, p->memcat);
p->sizetypeinfo = headersize + typesize;
if (headersize == 4)
{
p->typeinfo[0] = (typesize & 127) | (1 << 7);
p->typeinfo[1] = typesize >> 7;
p->typeinfo[2] = 0;
p->typeinfo[3] = 0;
}
else
{
p->typeinfo[0] = uint8_t(typesize);
p->typeinfo[1] = 0;
p->typeinfo[2] = 0;
}
memcpy(p->typeinfo + headersize, types, typesize);
}
offset += typesize;
}
else if (typesversion == 2 || typesversion == 3)
{
uint32_t typesize = readVarInt(data, size, offset);
if (typesize)
{
uint8_t* types = (uint8_t*)data + offset;
p->typeinfo = luaM_newarray(L, typesize, uint8_t, p->memcat);
p->sizetypeinfo = typesize;
memcpy(p->typeinfo, types, typesize);
offset += typesize;
if (typesversion == 3)
{
remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit);
}
}
}
}
const int sizecode = readVarInt(data, size, offset);
p->code = luaM_newarray(L, sizecode, Instruction, p->memcat);
p->sizecode = sizecode;
for (int j = 0; j < p->sizecode; ++j)
p->code[j] = read<uint32_t>(data, size, offset);
p->codeentry = p->code;
const int sizek = readVarInt(data, size, offset);
p->k = luaM_newarray(L, sizek, TValue, p->memcat);
p->sizek = sizek;
// Initialize the constants to nil to ensure they have a valid state
// in the event that some operation in the following loop fails with
// an exception.
for (int j = 0; j < p->sizek; ++j)
{
setnilvalue(&p->k[j]);
}
for (int j = 0; j < p->sizek; ++j)
{
switch (read<uint8_t>(data, size, offset))
{
case LBC_CONSTANT_NIL:
// All constants have already been pre-initialized to nil
break;
case LBC_CONSTANT_BOOLEAN:
{
uint8_t v = read<uint8_t>(data, size, offset);
setbvalue(&p->k[j], v);
break;
}
case LBC_CONSTANT_NUMBER:
{
double v = read<double>(data, size, offset);
setnvalue(&p->k[j], v);
break;
}
case LBC_CONSTANT_VECTOR:
{
float x = read<float>(data, size, offset);
float y = read<float>(data, size, offset);
float z = read<float>(data, size, offset);
float w = read<float>(data, size, offset);
(void)w;
setvvalue(&p->k[j], x, y, z, w);
break;
}
case LBC_CONSTANT_STRING:
{
TString* v = readString(strings, data, size, offset);
setsvalue(L, &p->k[j], v);
break;
}
case LBC_CONSTANT_IMPORT:
{
uint32_t iid = read<uint32_t>(data, size, offset);
resolveImportSafe(L, envt, p->k, iid);
setobj(L, &p->k[j], L->top - 1);
L->top--;
break;
}
case LBC_CONSTANT_TABLE:
{
int keys = readVarInt(data, size, offset);
LuaTable* h = luaH_new(L, 0, keys);
for (int i = 0; i < keys; ++i)
{
int key = readVarInt(data, size, offset);
TValue* val = luaH_set(L, h, &p->k[key]);
setnvalue(val, 0.0);
}
sethvalue(L, &p->k[j], h);
break;
}
case LBC_CONSTANT_CLOSURE:
{
uint32_t fid = readVarInt(data, size, offset);
Closure* cl = luaF_newLclosure(L, protos[fid]->nups, envt, protos[fid]);
cl->preload = (cl->nupvalues > 0);
setclvalue(L, &p->k[j], cl);
break;
}
default:
LUAU_ASSERT(!"Unexpected constant kind");
}
}
const int sizep = readVarInt(data, size, offset);
p->p = luaM_newarray(L, sizep, Proto*, p->memcat);
p->sizep = sizep;
for (int j = 0; j < p->sizep; ++j)
{
uint32_t fid = readVarInt(data, size, offset);
p->p[j] = protos[fid];
}
p->linedefined = readVarInt(data, size, offset);
p->debugname = readString(strings, data, size, offset);
uint8_t lineinfo = read<uint8_t>(data, size, offset);
if (lineinfo)
{
p->linegaplog2 = read<uint8_t>(data, size, offset);
int intervals = ((p->sizecode - 1) >> p->linegaplog2) + 1;
int absoffset = (p->sizecode + 3) & ~3;
const int sizelineinfo = absoffset + intervals * sizeof(int);
p->lineinfo = luaM_newarray(L, sizelineinfo, uint8_t, p->memcat);
p->sizelineinfo = sizelineinfo;
p->abslineinfo = (int*)(p->lineinfo + absoffset);
uint8_t lastoffset = 0;
for (int j = 0; j < p->sizecode; ++j)
{
lastoffset += read<uint8_t>(data, size, offset);
p->lineinfo[j] = lastoffset;
}
int lastline = 0;
for (int j = 0; j < intervals; ++j)
{
lastline += read<int32_t>(data, size, offset);
p->abslineinfo[j] = lastline;
}
}
uint8_t debuginfo = read<uint8_t>(data, size, offset);
if (debuginfo)
{
const int sizelocvars = readVarInt(data, size, offset);
p->locvars = luaM_newarray(L, sizelocvars, LocVar, p->memcat);
p->sizelocvars = sizelocvars;
for (int j = 0; j < p->sizelocvars; ++j)
{
p->locvars[j].varname = readString(strings, data, size, offset);
p->locvars[j].startpc = readVarInt(data, size, offset);
p->locvars[j].endpc = readVarInt(data, size, offset);
p->locvars[j].reg = read<uint8_t>(data, size, offset);
}
const int sizeupvalues = readVarInt(data, size, offset);
LUAU_ASSERT(sizeupvalues == p->nups);
p->upvalues = luaM_newarray(L, sizeupvalues, TString*, p->memcat);
p->sizeupvalues = sizeupvalues;
for (int j = 0; j < p->sizeupvalues; ++j)
{
p->upvalues[j] = readString(strings, data, size, offset);
}
}
protos[i] = p;
}
// "main" proto is pushed to Lua stack
uint32_t mainid = readVarInt(data, size, offset);
Proto* main = protos[mainid];
luaC_threadbarrier(L);
Closure* cl = luaF_newLclosure(L, 0, envt, main);
setclvalue(L, L->top, cl);
incr_top(L);
return 0;
}
int luau_load_DEPRECATED(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
{ {
size_t offset = 0; size_t offset = 0;
@ -592,3 +964,54 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
return 0; return 0;
} }
int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
{
if (!FFlag::LuauLoadNoOomThrow)
return luau_load_DEPRECATED(L, chunkname, data, size, env);
// we will allocate a fair amount of memory so check GC before we do
luaC_checkGC(L);
// pause GC for the duration of deserialization - some objects we're creating aren't rooted
const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX};
struct LoadContext
{
TempBuffer<TString*> strings;
TempBuffer<Proto*> protos;
const char* chunkname;
const char* data;
size_t size;
int env;
int result;
static void run(lua_State* L, void* ud)
{
LoadContext* ctx = (LoadContext*)ud;
ctx->result = loadsafe(L, ctx->strings, ctx->protos, ctx->chunkname, ctx->data, ctx->size, ctx->env);
}
} ctx = {
{},
{},
chunkname,
data,
size,
env,
};
int status = luaD_rawrunprotected(L, &LoadContext::run, &ctx);
// load can either succeed or get an OOM error, any other errors should be handled internally
LUAU_ASSERT(status == LUA_OK || status == LUA_ERRMEM);
if (status == LUA_ERRMEM)
{
lua_pushstring(L, LUA_MEMERRMSG); // out-of-memory error message doesn't require an allocation
return 1;
}
return ctx.result;
}

View file

@ -85,8 +85,8 @@ set(LUAU_PB_SOURCES ${LUAU_PB_DIR}/luau.pb.cc ${LUAU_PB_DIR}/luau.pb.h)
add_custom_command( add_custom_command(
OUTPUT ${LUAU_PB_SOURCES} OUTPUT ${LUAU_PB_SOURCES}
COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAU_PB_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${LUAU_PB_DIR}
COMMAND $<TARGET_FILE:protobuf::protoc> ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR} COMMAND ${protobuf_PROTOC} ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto --proto_path=${CMAKE_CURRENT_SOURCE_DIR} --cpp_out=${LUAU_PB_DIR}
DEPENDS protobuf::protoc ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto DEPENDS ${protobuf_PROTOC} ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto
) )
add_executable(Luau.Fuzz.Proto) add_executable(Luau.Fuzz.Proto)

View file

@ -11,7 +11,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
struct JsonEncoderFixture struct JsonEncoderFixture
@ -460,9 +459,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
AstStat* expr = expectParseStatement("@checked function a(b) return c end"); AstStat* expr = expectParseStatement("@checked function a(b) return c end");
std::string_view expected = std::string_view expected =
FFlag::LuauFixFunctionWithAttributesStartLocation R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
? R"({"type":"AstStatFunction","location":"0,0 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,0 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})"
: R"({"type":"AstStatFunction","location":"0,9 - 0,35","name":{"type":"AstExprGlobal","location":"0,18 - 0,19","global":"a"},"func":{"type":"AstExprFunction","location":"0,9 - 0,35","attributes":[{"type":"AstAttr","location":"0,0 - 0,8","name":"checked"}],"generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,20 - 0,21"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,22 - 0,32","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,23 - 0,31","list":[{"type":"AstExprGlobal","location":"0,30 - 0,31","global":"c"}]}]},"functionDepth":1,"debugname":"a"}})";
CHECK(toJson(expr) == expected); CHECK(toJson(expr) == expected);
} }

View file

@ -19,10 +19,7 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
using namespace Luau; using namespace Luau;
@ -3121,6 +3118,20 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
CHECK_EQ(ac.context, AutocompleteContext::String); CHECK_EQ(ac.context, AutocompleteContext::String);
} }
TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key_iso")
{
check(R"(
type Direction = "up" | "down"
local b: {[Direction]: boolean} = {["@2"] = true}
)");
auto ac = autocomplete('2');
CHECK(ac.entryMap.count("up"));
CHECK(ac.entryMap.count("down"));
}
TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key") TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key")
{ {
check(R"( check(R"(
@ -4431,7 +4442,6 @@ local x = 1 + result.
TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
{ {
ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true};
check(R"( check(R"(
local table1: {ChildClass} = {} local table1: {ChildClass} = {}
local table2 = {} local table2 = {}
@ -4443,17 +4453,24 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
)"); )");
auto ac = autocomplete('1'); auto ac = autocomplete('1');
// RIDE-11517: This should *really* be the members of `ChildClass`, but
// would previously stack overflow. if (FFlag::LuauSolverV2 && FFlag::LuauNonReentrantGeneralization3)
{
// This `if` statement is because `LuauNonReentrantGeneralization3`
// sets some flags
CHECK(ac.entryMap.count("BaseMethod") > 0);
CHECK(ac.entryMap.count("Method") > 0);
}
else
{
// Otherwise, we don't infer anything for `value`, which is _fine_.
CHECK(ac.entryMap.empty()); CHECK(ac.entryMap.empty());
} }
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions") TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions")
{ {
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
check(R"( check(R"(
@ -4468,10 +4485,6 @@ end
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_private_scope") TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_private_scope")
{ {
// Needs new global initialization in the Fixture, but can't place the flag inside the base Fixture
if (!FFlag::LuauUserTypeFunTypecheck)
return;
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
// Global scope polution by the embedder has no effect // Global scope polution by the embedder has no effect
@ -4504,7 +4517,6 @@ this@2
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_eval_in_autocomplete") TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_eval_in_autocomplete")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauTypeFunResultInAutocomplete{FFlag::LuauTypeFunResultInAutocomplete, true};
check(R"( check(R"(
type function foo(x) type function foo(x)

View file

@ -23,6 +23,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip)
using namespace Luau; using namespace Luau;
@ -2971,6 +2972,33 @@ TEST_CASE("TypeFunction")
CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions)); CHECK_NOTHROW(Luau::compileOrThrow(bcb, "type function a() return types.any end", options, parseOptions));
} }
TEST_CASE("NoTypeFunctionsInBytecode")
{
ScopedFastFlag luauCompileFixTypeFunctionSkip{FFlag::LuauCompileFixTypeFunctionSkip, true};
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::compileOrThrow(bcb, R"(
type function a() return types.any end
function b() return 2 end
return b()
)");
CHECK_EQ("\n" + bcb.dumpEverything(), R"(
Function 0 (b):
LOADN R0 2
RETURN R0 1
Function 1 (??):
DUPCLOSURE R0 K0 ['b']
SETGLOBAL R0 K1 ['b']
GETGLOBAL R0 K1 ['b']
CALL R0 0 -1
RETURN R0 -1
)");
}
TEST_CASE("DebugLineInfo") TEST_CASE("DebugLineInfo")
{ {
Luau::BytecodeBuilder bcb; Luau::BytecodeBuilder bcb;

View file

@ -31,10 +31,15 @@ extern int optimizationLevel;
void luaC_fullgc(lua_State* L); void luaC_fullgc(lua_State* L);
void luaC_validate(lua_State* L); 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(DebugLuauAbortingChecks) LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations) LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAG(LuauLoadNoOomThrow)
static lua_CompileOptions defaultOptions() static lua_CompileOptions defaultOptions()
{ {
@ -1424,6 +1429,119 @@ TEST_CASE("Debugger")
CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often
} }
TEST_CASE("InterruptInspection")
{
ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true};
static bool skipbreak = false;
runConformance(
"basic.luau",
[](lua_State* L)
{
lua_Callbacks* cb = lua_callbacks(L);
cb->interrupt = [](lua_State* L, int gc)
{
if (gc >= 0)
return;
if (!lua_isyieldable(L))
return;
if (!skipbreak)
lua_break(L);
skipbreak = !skipbreak;
};
},
[](lua_State* L)
{
// Debug info can be retrieved from every location
lua_Debug ar = {};
CHECK(lua_getinfo(L, 0, "nsl", &ar));
// Simulating a hook being called from the original break location
luau_callhook(
L,
[](lua_State* L, lua_Debug* ar)
{
CHECK(lua_getinfo(L, 0, "nsl", ar));
},
nullptr
);
},
nullptr,
nullptr,
/* skipCodegen */ true
);
}
TEST_CASE("InterruptErrorInspection")
{
ScopedFastFlag luauCurrentLineBounds{FFlag::LuauCurrentLineBounds, true};
// for easy access in no-capture lambda
static int target = 0;
static int step = 0;
std::string source = R"(
function fib(n)
return n < 2 and 1 or fib(n - 1) + fib(n - 2)
end
fib(5)
)";
for (target = 0; target < 20; target++)
{
step = 0;
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
luaL_openlibs(L);
luaL_sandbox(L);
luaL_sandboxthread(L);
size_t bytecodeSize = 0;
char* bytecode = luau_compile(source.data(), source.size(), nullptr, &bytecodeSize);
int result = luau_load(L, "=InterruptErrorInspection", bytecode, bytecodeSize, 0);
free(bytecode);
REQUIRE(result == LUA_OK);
lua_Callbacks* cb = lua_callbacks(L);
cb->interrupt = [](lua_State* L, int gc)
{
if (gc >= 0)
return;
if (step == target)
luaL_error(L, "test");
step++;
};
lua_resume(L, nullptr, 0);
// Debug info can be retrieved from every location
lua_Debug ar = {};
CHECK(lua_getinfo(L, 0, "nsl", &ar));
// Simulating a hook being called from the original break location
luau_callhook(
L,
[](lua_State* L, lua_Debug* ar)
{
CHECK(lua_getinfo(L, 0, "nsl", ar));
},
nullptr
);
}
}
TEST_CASE("NDebugGetUpValue") TEST_CASE("NDebugGetUpValue")
{ {
lua_CompileOptions copts = defaultOptions(); lua_CompileOptions copts = defaultOptions();
@ -2495,6 +2613,46 @@ TEST_CASE("UserdataApi")
CHECK(dtorhits == 42); CHECK(dtorhits == 42);
} }
// provide alignment of 16 for userdata objects with size of 16 and up as long as the Luau allocation functions supports it
TEST_CASE("UserdataAlignment")
{
const auto testAllocate = [](void* ud, void* ptr, size_t osize, size_t nsize) -> void*
{
if (nsize == 0)
{
::operator delete(ptr, std::align_val_t(16));
return nullptr;
}
else if (osize == 0)
{
return ::operator new(nsize, std::align_val_t(16));
}
// resize is unreachable in this test and is omitted
return nullptr;
};
StateRef globalState(lua_newstate(testAllocate, nullptr), lua_close);
lua_State* L = globalState.get();
for (int size = 16; size <= 4096; size += 4)
{
for (int i = 0; i < 10; i++)
{
void* data = lua_newuserdata(L, size);
LUAU_ASSERT(uintptr_t(data) % 16 == 0);
lua_pop(L, 1);
}
for (int i = 0; i < 10; i++)
{
void* data = lua_newuserdatadtor(L, size, [](void*) {});
LUAU_ASSERT(uintptr_t(data) % 16 == 0);
lua_pop(L, 1);
}
}
}
TEST_CASE("LightuserdataApi") TEST_CASE("LightuserdataApi")
{ {
StateRef globalState(luaL_newstate(), lua_close); StateRef globalState(luaL_newstate(), lua_close);
@ -3016,6 +3174,8 @@ TEST_CASE("HugeFunction")
TEST_CASE("HugeFunctionLoadFailure") TEST_CASE("HugeFunctionLoadFailure")
{ {
ScopedFastFlag luauLoadNoOomThrow{FFlag::LuauLoadNoOomThrow, true};
// This test case verifies that if an out-of-memory error occurs inside of // This test case verifies that if an out-of-memory error occurs inside of
// luau_load, we are not left with any GC objects in inconsistent states // luau_load, we are not left with any GC objects in inconsistent states
// that would cause issues during garbage collection. // that would cause issues during garbage collection.
@ -3066,15 +3226,11 @@ TEST_CASE("HugeFunctionLoadFailure")
luaL_sandbox(L); luaL_sandbox(L);
luaL_sandboxthread(L); luaL_sandboxthread(L);
try int status = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
{ REQUIRE(status == 1);
luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
REQUIRE(false); // The luau_load should fail with an exception const char* error = lua_tostring(L, -1);
} CHECK(strcmp(error, "not enough memory") == 0);
catch (const std::exception& ex)
{
REQUIRE(strcmp(ex.what(), "lua_exception: not enough memory") == 0);
}
luaC_fullgc(L); luaC_fullgc(L);
} }

View file

@ -14,6 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
struct DataFlowGraphFixture struct DataFlowGraphFixture
{ {
@ -46,6 +47,17 @@ struct DataFlowGraphFixture
REQUIRE(node); REQUIRE(node);
return graph->getDef(node); return graph->getDef(node);
} }
void checkOperands(const Phi* phi, std::vector<DefId> operands)
{
Set<const Def*> operandSet{nullptr};
for (auto o : operands)
operandSet.insert(o.get());
CHECK(phi->operands.size() == operandSet.size());
for (auto o : phi->operands)
CHECK(operandSet.contains(o.get()));
}
}; };
TEST_SUITE_BEGIN("DataFlowGraphBuilder"); TEST_SUITE_BEGIN("DataFlowGraphBuilder");
@ -119,6 +131,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while")
{ {
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"( dfg(R"(
local x local x
@ -133,8 +147,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1); auto phi = get<Phi>(x2);
CHECK(x1 == x2); REQUIRE(phi);
checkOperands(phi, {x0, x1});
} }
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while")
@ -157,6 +172,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_while")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat")
{ {
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"( dfg(R"(
local x local x
@ -171,7 +188,7 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1); CHECK(x0 != x1);
CHECK(x1 == x2); CHECK(x1 == x2);
} }
@ -195,6 +212,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_repeat")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for")
{ {
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"( dfg(R"(
local x local x
@ -209,8 +228,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1); auto phi = get<Phi>(x2);
CHECK(x1 == x2); REQUIRE(phi);
checkOperands(phi, {x0, x1});
} }
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for")
@ -233,6 +253,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in")
{ {
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"( dfg(R"(
local x local x
@ -247,8 +269,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for_in")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1); auto phi = get<Phi>(x2);
CHECK(x1 == x2); REQUIRE(phi);
checkOperands(phi, {x0, x1});
} }
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in")
@ -271,6 +294,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_owned_by_for_in")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_by_while")
{ {
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"( dfg(R"(
local t = {} local t = {}
t.x = 5 t.x = 5
@ -286,8 +311,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_preexisting_property_not_owned_b
DefId x2 = getDef<AstExprIndexName, 2>(); // t.x = true DefId x2 = getDef<AstExprIndexName, 2>(); // t.x = true
DefId x3 = getDef<AstExprIndexName, 3>(); // local y = t.x DefId x3 = getDef<AstExprIndexName, 3>(); // local y = t.x
CHECK(x1 == x2); auto phi = get<Phi>(x3);
CHECK(x2 == x3); REQUIRE(phi);
checkOperands(phi, {x1, x2});
} }
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_non_preexisting_property_not_owned_by_while") TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_non_preexisting_property_not_owned_by_while")

View file

@ -33,7 +33,6 @@ LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAG(LuauCloneTypeAliasBindings) LUAU_FASTFLAG(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings) LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauBetterScopeSelection) LUAU_FASTFLAG(LuauBetterScopeSelection)
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
@ -3065,8 +3064,6 @@ z = a.P.E
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local") TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local")
{ {
ScopedFastFlag luauUserTypeFunTypecheck{FFlag::LuauUserTypeFunTypecheck, true};
const std::string source = R"(--!strict const std::string source = R"(--!strict
type function foo(x: type): type type function foo(x: type): type
if x.tag == "singleton" then if x.tag == "singleton" then

View file

@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace namespace
{ {
@ -876,6 +877,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "discard_type_graphs")
TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded") TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded")
{ {
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
Frontend fe{&fileResolver, &configResolver, {false}}; Frontend fe{&fileResolver, &configResolver, {false}};
fileResolver.source["Module/A"] = R"( fileResolver.source["Module/A"] = R"(
@ -892,13 +895,9 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
// It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION> // It could segfault, or you could see weird type names like the empty string or <VALUELESS BY EXCEPTION>
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
REQUIRE_EQ( CHECK_EQ(
"Type\n\t" "Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'",
"'{ count: string }'" toString(result.errors[0]));
"\ncould not be converted into\n\t"
"'{ Count: number }'",
toString(result.errors[0])
);
} }
else else
REQUIRE_EQ( REQUIRE_EQ(

View file

@ -15,7 +15,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
@ -226,7 +226,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a")
TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})")
{ {
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
TableType tt; TableType tt;
tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType};
@ -260,7 +260,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?")
TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
{ {
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
auto [aTy, aFree] = freshType(); auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType(); auto [bTy, bFree] = freshType();

View file

@ -8,13 +8,13 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauNonReentrantGeneralization2); LUAU_FASTFLAG(LuauNonReentrantGeneralization3);
TEST_SUITE_BEGIN("InferPolarity"); TEST_SUITE_BEGIN("InferPolarity");
TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }") TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
{ {
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true}; ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
TypeArena arena; TypeArena arena;
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack); ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute); LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauNonReentrantGeneralization2); LUAU_FASTFLAG(LuauNonReentrantGeneralization3);
using namespace Luau; using namespace Luau;
@ -1942,7 +1942,7 @@ print(foo:bar(2.0))
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{ {
// FIXME: For now this flag causes a stack overflow on Windows. // FIXME: For now this flag causes a stack overflow on Windows.
ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization2, false}; ScopedFastFlag _{FFlag::LuauNonReentrantGeneralization3, false};
LintResult result = lint(R"( LintResult result = lint(R"(
local t = {} local t = {}

View file

@ -15,8 +15,6 @@
#include "doctest.h" #include "doctest.h"
#include <iostream> #include <iostream>
LUAU_FASTFLAG(LuauNewNonStrictWarnOnUnknownGlobals)
LUAU_FASTFLAG(LuauNonStrictVisitorImprovements)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
using namespace Luau; using namespace Luau;
@ -362,8 +360,6 @@ end
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2")
{ {
ScopedFastFlag luauNonStrictVisitorImprovements{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"( CheckResult result = checkNonStrict(R"(
local t = {function(x) local t = {function(x)
abs(x) abs(x)
@ -510,8 +506,6 @@ foo.bar("hi")
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked")
{ {
ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"( CheckResult result = checkNonStrict(R"(
local foo = (abs("foo")) local foo = (abs("foo"))
)"); )");
@ -527,8 +521,6 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "exprgroup_is_checked")
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "binop_is_checked") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "binop_is_checked")
{ {
ScopedFastFlag sff{FFlag::LuauNonStrictVisitorImprovements, true};
CheckResult result = checkNonStrict(R"( CheckResult result = checkNonStrict(R"(
local foo = 4 + abs("foo") local foo = 4 + abs("foo")
)"); )");
@ -653,8 +645,6 @@ TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_method_calls")
TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_non_strict")
{ {
ScopedFastFlag flags[] = {{FFlag::LuauNonStrictVisitorImprovements, true}, {FFlag::LuauNewNonStrictWarnOnUnknownGlobals, true}};
CheckResult result = check(Mode::Nonstrict, R"( CheckResult result = check(Mode::Nonstrict, R"(
foo = 5 foo = 5
local wrong1 = foob local wrong1 = foob

View file

@ -15,10 +15,14 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles) LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauTypePackDetectCycles) LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
using namespace Luau; using namespace Luau;
@ -1068,19 +1072,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
CHECK("'a & (false?)" == toString(result)); CHECK("'a & (false?)" == toString(result));
} }
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_recursive_metatable")
{
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNormalizationCatchMetatableCycles, true}};
TypeId root = arena.addType(BlockedType{});
TypeId emptyTable = arena.addType(TableType(TableState::Sealed, {}));
TypeId metatable = arena.addType(MetatableType{emptyTable, root});
emplaceType<BoundType>(asMutable(root), metatable);
auto normalized = normalizer.normalize(root);
REQUIRE(normalized);
CHECK_EQ("t1 where t1 = { @metatable t1, { } }", toString(normalizer.typeFromNormal(*normalized)));
}
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow") TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
{ {
if (!FFlag::LuauSolverV2) if (!FFlag::LuauSolverV2)
@ -1186,12 +1177,20 @@ end
)"); )");
} }
#if 0
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_limit_function_intersection_complexity")
{ {
ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 80}; ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 80};
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50}; ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20}; ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag _[] = {
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::DebugLuauGreedyGeneralization, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
function _(_).readu32(l0) function _(_).readu32(l0)
return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil return ({[_(_(_))]=_,[_(if _ then _)]=_,n0=_,})[_],nil
@ -1202,13 +1201,21 @@ _(_)[_(n32)] %= _(_(_))
LUAU_REQUIRE_ERRORS(result); LUAU_REQUIRE_ERRORS(result);
} }
#if !(defined(_WIN32) && !(defined(_M_X64) || defined(_M_ARM64)))
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_propagate_normalization_failures")
{ {
ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50}; ScopedFastInt luauNormalizeIntersectionLimit{FInt::LuauNormalizeIntersectionLimit, 50};
ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20}; ScopedFastInt luauNormalizeUnionLimit{FInt::LuauNormalizeUnionLimit, 20};
ScopedFastFlag luauSubtypingEnableReasoningLimit{FFlag::LuauSubtypingEnableReasoningLimit, true};
ScopedFastFlag luauTurnOffNonreentrantGeneralization{FFlag::LuauNonReentrantGeneralization2, false}; ScopedFastFlag _[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::LuauSimplifyOutOfLine, true},
{FFlag::LuauNonReentrantGeneralization3, false},
{FFlag::DebugLuauGreedyGeneralization, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauSubtypeGenericsAndNegations, true},
};
CheckResult result = check(R"( CheckResult result = check(R"(
function _(_,"").readu32(l0) function _(_,"").readu32(l0)
@ -1223,7 +1230,7 @@ _().readu32 %= _(_(_(_),_))
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_flatten_type_pack_cycle") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_flatten_type_pack_cycle")
{ {
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypePackDetectCycles, true}}; ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}};
// Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check // Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check
CHECK_THROWS_AS( CHECK_THROWS_AS(
@ -1243,15 +1250,19 @@ do end
#if 0 #if 0
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
{ {
// FIXME? This test code happens not to ICE with eager generalization ScopedFastFlag sff[] = {
// enabled. This could either be because the problem is fixed, or because {FFlag::LuauSolverV2, true},
// another bug is obscuring the problem. {FFlag::LuauRefineWaitForBlockedTypesInTarget, true},
if (FFlag::DebugLuauGreedyGeneralization) {FFlag::LuauSimplifyOutOfLine, true},
return; {FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::DebugLuauGreedyGeneralization, true}
};
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypePackDetectCycles, true}}; // FIXME CLI-153131: This is constructing a cyclic type pack
// Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check
CHECK_THROWS_AS( CHECK_THROWS_AS(
check(R"( check(R"(
function _(_).n0(l32,...) function _(_).n0(l32,...)

Some files were not shown because too many files have changed in this diff Show more