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,13 +489,10 @@ 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))
{ innerSeen.insert(ty);
if (is<UnionType, IntersectionType>(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,58 +481,55 @@ 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
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings;
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined
static const char* typeFunctionRuntimeBindings[] = {
// Libraries
"math",
"table",
"string",
"bit32",
"utf8",
"buffer",
// Globals
"assert",
"error",
"print",
"next",
"ipairs",
"pairs",
"select",
"unpack",
"getmetatable",
"setmetatable",
"rawget",
"rawset",
"rawlen",
"rawequal",
"tonumber",
"tostring",
"type",
"typeof",
};
for (auto& name : typeFunctionRuntimeBindings)
{ {
// Global scope cannot be the parent of the type checking environment because it can be changed by the embedder AstName astName = globals.globalNames.names->get(name);
globals.globalTypeFunctionScope->exportedTypeBindings = globals.globalScope->exportedTypeBindings; LUAU_ASSERT(astName.value);
globals.globalTypeFunctionScope->builtinTypeNames = globals.globalScope->builtinTypeNames;
// Type function runtime also removes a few standard libraries and globals, so we will take only the ones that are defined globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
static const char* typeFunctionRuntimeBindings[] = {
// Libraries
"math",
"table",
"string",
"bit32",
"utf8",
"buffer",
// Globals
"assert",
"error",
"print",
"next",
"ipairs",
"pairs",
"select",
"unpack",
"getmetatable",
"setmetatable",
"rawget",
"rawset",
"rawlen",
"rawequal",
"tonumber",
"tostring",
"type",
"typeof",
};
for (auto& name : typeFunctionRuntimeBindings)
{
AstName astName = globals.globalNames.names->get(name);
LUAU_ASSERT(astName.value);
globals.globalTypeFunctionScope->bindings[astName] = globals.globalScope->bindings[astName];
}
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
);
LUAU_ASSERT(typeFunctionLoadResult.success);
finalizeGlobalBindings(globals.globalTypeFunctionScope);
} }
LoadDefinitionFileResult typeFunctionLoadResult = frontend.loadDefinitionFile(
globals, globals.globalTypeFunctionScope, getTypeFunctionDefinitionSource(), "@luau", /* captureComments */ false, false
);
LUAU_ASSERT(typeFunctionLoadResult.success);
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
{ ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope);
// Create module-local scope for the type function environment localTypeFunctionScope->location = block->location;
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(typeFunctionScope); typeFunctionRuntime->rootScope = localTypeFunctionScope;
localTypeFunctionScope->location = block->location;
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,8 +828,7 @@ 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
if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value)) if (scope->exportedTypeBindings.count(function->name.value) || scope->privateTypeBindings.count(function->name.value))
@ -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,7 +908,7 @@ 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
@ -922,13 +916,10 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
{ {
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
{ TypeId bt = arena->addType(BlockedType{});
// Similar to global pre-population, create a binding for each type function in the scope upfront typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location};
TypeId bt = arena->addType(BlockedType{}); astTypeFunctionEnvironmentScopes[function] = typeFunctionEnvScope;
typeFunctionEnvScope->bindings[function->name] = Binding{bt, function->location};
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,61 +939,32 @@ 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))
{ return;
if (userFuncData.environment.find(name))
return;
if (auto ty = get<TypeFunctionInstanceType>(type); ty && ty->userFuncData.definition) if (auto ty = get<TypeFunctionInstanceType>(type); ty && ty->userFuncData.definition)
{
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition))
{ {
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false))
scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location};
if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition))
{
if (auto existing = (*it)->linearSearchForBinding(name, /* traverseScopeChain */ false))
scope->bindings[ty->userFuncData.definition->name] =
Binding{existing->typeId, ty->userFuncData.definition->location};
}
} }
};
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
for (auto& [name, tf] : curr->exportedTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
level++;
} }
} };
else
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{ {
for (Scope* curr = scope.get(); curr; curr = curr->parent.get()) for (auto& [name, tf] : curr->privateTypeBindings)
{ addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition) for (auto& [name, tf] : curr->exportedTypeBindings)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level);
}
for (auto& [name, tf] : curr->exportedTypeBindings) level++;
{
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,24 +1457,21 @@ 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->lvalueTypes[def] = sig.signature;
sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; sig.bodyScope->rvalueRefinements[def] = sig.signature;
sig.bodyScope->lvalueTypes[def] = sig.signature; }
sig.bodyScope->rvalueRefinements[def] = sig.signature; else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
} {
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>()) sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
{ sig.bodyScope->lvalueTypes[def] = sig.signature;
sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; sig.bodyScope->rvalueRefinements[def] = sig.signature;
sig.bodyScope->lvalueTypes[def] = sig.signature; }
sig.bodyScope->rvalueRefinements[def] = sig.signature; else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
} {
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>()) sig.bodyScope->rvalueRefinements[def] = sig.signature;
{
sig.bodyScope->rvalueRefinements[def] = sig.signature;
}
} }
checkFunctionBody(sig.bodyScope, function->func); checkFunctionBody(sig.bodyScope, function->func);
@ -1782,15 +1753,9 @@ 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) if (function->name == "typeof")
return ControlFlow::None;
if (FFlag::LuauNoTypeFunctionsNamedTypeOf)
{ {
if (function->name == "typeof") reportError(function->location, ReservedIdentifier{"typeof"});
{
reportError(function->location, ReservedIdentifier{"typeof"});
}
} }
auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); auto scopePtr = astTypeFunctionEnvironmentScopes.find(function);
@ -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,8 +2443,7 @@ 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);
module->astTypes[expr] = result.ty; module->astTypes[expr] = 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,18 +3221,47 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
{ {
LUAU_ASSERT(!indexValueLowerBound.empty()); LUAU_ASSERT(!indexValueLowerBound.empty());
TypeId indexKey = indexKeyLowerBound.size() == 1 if (FFlag::LuauSimplifyOutOfLine)
? *indexKeyLowerBound.begin() {
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); TypeId indexKey = nullptr;
TypeId indexValue = nullptr;
TypeId indexValue = indexValueLowerBound.size() == 1 if (indexKeyLowerBound.size() == 1)
? *indexValueLowerBound.begin() {
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); indexKey = *indexKeyLowerBound.begin();
}
else
{
indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
unionsToSimplify.push_back(indexKey);
}
ttv->indexer = TableIndexer{indexKey, indexValue}; 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
? *indexKeyLowerBound.begin()
: arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())});
TypeId indexValue = indexValueLowerBound.size() == 1
? *indexValueLowerBound.begin()
: arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())});
ttv->indexer = TableIndexer{indexKey, indexValue};
}
} }
if (expectedType) if (expectedType && !FFlag::LuauTableLiteralSubtypeSpecificCheck)
{ {
addConstraint( addConstraint(
scope, scope,
@ -3769,15 +3774,10 @@ 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);
else else
@ -3790,24 +3790,9 @@ 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);
else else
@ -3820,19 +3805,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>())
{ {
@ -4036,9 +4008,26 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location,
if (get<NeverType>(follow(rhs))) if (get<NeverType>(follow(rhs)))
return lhs; return lhs;
TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); 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);
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,12 +4172,9 @@ 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
{ GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
// Handle type function globals as well, without preparing a module scope since they have a separate environment program->visit(&tfgp);
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp);
}
} }
void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program) void ConstraintGenerator::prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program)
@ -4200,12 +4186,9 @@ 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
{ GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
// Handle type function globals as well, without preparing a module scope since they have a separate environment program->visit(&tfgp);
GlobalPrepopulator tfgp{NotNull{typeFunctionRuntime->rootScope.get()}, arena, dfg};
program->visit(&tfgp);
}
} }
bool ConstraintGenerator::recordPropertyAssignment(TypeId ty) bool ConstraintGenerator::recordPropertyAssignment(TypeId ty)
@ -4265,9 +4248,16 @@ 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)
{
scope->bindings[symbol] = Binding{ty, location}; TypeId ty = makeUnion(std::move(tys));
scope->bindings[symbol] = Binding{ty, location};
}
else
{
TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location);
scope->bindings[symbol] = Binding{ty, location};
}
} }
} }
} }
@ -4305,7 +4295,12 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
else if (result.size() == 1) else if (result.size() == 1)
el = result[0]; el = result[0];
else else
el = module->internalTypes.addType(UnionType{std::move(result)}); {
if (FFlag::LuauSimplifyOutOfLine)
el = makeUnion(std::move(result));
else
el = module->internalTypes.addType(UnionType{std::move(result)});
}
} }
} }
}; };

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;
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; 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)
{ uninhabitedTypeFunctions.insert(ity);
for (TypeId ity : result.irreducibleTypes)
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
{ {
@ -40,18 +42,11 @@ struct PushScope
~PushScope() ~PushScope()
{ {
if (FFlag::LuauDfgScopeStackTrueReset) // If somehow this stack has _shrunk_ to be smaller than we expect,
{ // something very strange has happened.
// If somehow this stack has _shrunk_ to be smaller than we expect, LUAU_ASSERT(stack.size() > previousSize);
// something very strange has happened. while (stack.size() > previousSize)
LUAU_ASSERT(stack.size() > previousSize);
while (stack.size() > previousSize)
stack.pop_back();
}
else
{
stack.pop_back(); stack.pop_back();
}
} }
}; };
@ -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,40 +534,96 @@ 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)
{ {
PushScope ps{scopeStack, whileScope};
visitExpr(w->condition); ControlFlow cf;
visit(w->body); {
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;
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope);
else else
currentScope_DEPRECATED()->inherit(whileScope); {
{
PushScope ps{scopeStack, whileScope};
visitExpr(w->condition);
visit(w->body);
}
return ControlFlow::None; if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(whileScope);
else
currentScope_DEPRECATED()->inherit(whileScope);
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)
{ {
PushScope ps{scopeStack, repeatScope}; ControlFlow cf;
visitBlockWithoutChildScope(r->body);
visitExpr(r->condition);
}
if (FFlag::LuauDfgScopeStackNotNull) {
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); currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
return ControlFlow::None; // `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};
visitBlockWithoutChildScope(r->body);
visitExpr(r->condition);
}
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(repeatScope);
else
currentScope_DEPRECATED()->inherit(repeatScope);
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,66 +701,135 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatFor* f)
if (f->step) if (f->step)
visitExpr(f->step); visitExpr(f->step);
if (FFlag::LuauDfgAllowUpdatesInLoops)
{ {
PushScope ps{scopeStack, forScope};
if (f->var->annotation) ControlFlow cf;
visitType(f->var->annotation); {
PushScope ps{scopeStack, forScope};
DefId def = defArena->freshCell(f->var, f->var->location); if (f->var->annotation)
graph.localDefs[f->var] = def; visitType(f->var->annotation);
if (FFlag::LuauDfgScopeStackNotNull)
DefId def = defArena->freshCell(f->var, f->var->location);
graph.localDefs[f->var] = def;
currentScope()->bindings[f->var] = def; currentScope()->bindings[f->var] = def;
else captures[f->var].allVersions.push_back(def);
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point cf = visit(f->body);
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;
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else else
currentScope_DEPRECATED()->inherit(forScope); {
{
PushScope ps{scopeStack, forScope};
return ControlFlow::None; if (f->var->annotation)
visitType(f->var->annotation);
DefId def = defArena->freshCell(f->var, f->var->location);
graph.localDefs[f->var] = def;
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->bindings[f->var] = def;
else
currentScope_DEPRECATED()->bindings[f->var] = def;
captures[f->var].allVersions.push_back(def);
// TODO(controlflow): entry point has a back edge from exit point
visit(f->body);
}
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
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)
{ {
PushScope ps{scopeStack, forScope};
for (AstLocal* local : f->vars) ControlFlow cf;
{ {
if (local->annotation) PushScope ps{scopeStack, forScope};
visitType(local->annotation);
DefId def = defArena->freshCell(local, local->location); for (AstLocal* local : f->vars)
graph.localDefs[local] = def; {
if (FFlag::LuauDfgScopeStackNotNull) if (local->annotation)
currentScope()->bindings[local] = def; visitType(local->annotation);
else
currentScope_DEPRECATED()->bindings[local] = def; DefId def = defArena->freshCell(local, local->location);
captures[local].allVersions.push_back(def); 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);
} }
// TODO(controlflow): entry point has a back edge from exit point auto scope = currentScope();
// We're gonna need a `visitExprList` and `visitVariadicExpr` (function calls and `...`) // If the inner loop unconditioanlly returns or throws we shouldn't
for (AstExpr* e : f->values) // consume any type state from the loop body.
visitExpr(e); if (!matches(cf, ControlFlow::Returns | ControlFlow::Throws))
join(scope, scope, forScope);
visit(f->body); return ControlFlow::None;
} }
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else else
currentScope_DEPRECATED()->inherit(forScope); {
{
PushScope ps{scopeStack, forScope};
return ControlFlow::None; 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);
visit(f->body);
}
if (FFlag::LuauDfgScopeStackNotNull)
currentScope()->inherit(forScope);
else
currentScope_DEPRECATED()->inherit(forScope);
return ControlFlow::None;
}
} }
ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a) ControlFlow DataFlowGraphBuilder::visit(AstStatAssign* a)
@ -1112,9 +1240,18 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b)
{ {
visitExpr(b->left); visitExpr(b->left);
visitExpr(b->right); if (FFlag::LuauDfgMatchCGScopes)
{
PushScope _{scopeStack, makeChildScope()};
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
else
{
visitExpr(b->right);
return {defArena->freshCell(Symbol{}, b->location), nullptr};
}
return {defArena->freshCell(Symbol{}, b->location), nullptr};
} }
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
@ -1127,9 +1264,29 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t)
DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprIfElse* i)
{ {
visitExpr(i->condition); if (FFlag::LuauDfgMatchCGScopes)
visitExpr(i->trueExpr); {
visitExpr(i->falseExpr); // 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->trueExpr);
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,8 +1105,7 @@ 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
DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler); DataFlowGraph dfg = DataFlowGraphBuilder::build(root, NotNull{&incrementalModule->defArena}, NotNull{&incrementalModule->keyArena}, iceHandler);
@ -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
{ ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope);
// Create module-local scope for the type function environment localTypeFunctionScope->location = root->location;
ScopePtr localTypeFunctionScope = std::make_shared<Scope>(cg.typeFunctionScope); cg.typeFunctionRuntime->rootScope = localTypeFunctionScope;
localTypeFunctionScope->location = root->location;
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,27 +1095,13 @@ 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);
}
catch (const Luau::InternalCompilerError&)
{
item.exception = std::current_exception();
}
} }
else catch (const Luau::InternalCompilerError&)
{ {
try item.exception = std::current_exception();
{
checkBuildQueueItem(item);
}
catch (...)
{
item.exception = std::current_exception();
}
} }
{ {
@ -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)
@ -368,26 +366,16 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatWhile* whileStatement) NonStrictContext visit(AstStatWhile* whileStatement)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue);
{ NonStrictContext body = visit(whileStatement->body);
NonStrictContext condition = visit(whileStatement->condition, ValueContext::RValue); return NonStrictContext::disjunction(builtinTypes, arena, condition, body);
NonStrictContext body = visit(whileStatement->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 condition = visit(repeatStatement->condition, ValueContext::RValue);
NonStrictContext body = visit(repeatStatement->body); return NonStrictContext::disjunction(builtinTypes, arena, body, condition);
NonStrictContext condition = visit(repeatStatement->condition, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, body, condition);
}
else
return {};
} }
NonStrictContext visit(AstStatBreak* breakStatement) NonStrictContext visit(AstStatBreak* breakStatement)
@ -402,13 +390,10 @@ 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
{ // for how the contexts are handled
// TODO: this is believing existing code, but i'm not sure if this makes sense for (AstExpr* expr : returnStatement->list)
// for how the contexts are handled visit(expr, ValueContext::RValue);
for (AstExpr* expr : returnStatement->list)
visit(expr, ValueContext::RValue);
}
return {}; return {};
} }
@ -430,21 +415,14 @@ 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?
{ if (forStatement->from)
// TODO: throwing out context based on same principle as existing code? visit(forStatement->from, ValueContext::RValue);
if (forStatement->from) if (forStatement->to)
visit(forStatement->from, ValueContext::RValue); visit(forStatement->to, ValueContext::RValue);
if (forStatement->to) if (forStatement->step)
visit(forStatement->to, ValueContext::RValue); visit(forStatement->step, ValueContext::RValue);
if (forStatement->step) return visit(forStatement->body);
visit(forStatement->step, ValueContext::RValue);
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)
{ visit(rhs, ValueContext::RValue);
for (AstExpr* rhs : forInStatement->values) return visit(forInStatement->body);
visit(rhs, ValueContext::RValue);
return visit(forInStatement->body);
}
else
{
return {};
}
} }
NonStrictContext visit(AstStatAssign* assign) NonStrictContext visit(AstStatAssign* assign)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) for (AstExpr* lhs : assign->vars)
{ visit(lhs, ValueContext::LValue);
for (AstExpr* lhs : assign->vars) for (AstExpr* rhs : assign->values)
visit(lhs, ValueContext::LValue); visit(rhs, ValueContext::RValue);
for (AstExpr* rhs : assign->values)
visit(rhs, ValueContext::RValue);
}
return {}; return {};
} }
NonStrictContext visit(AstStatCompoundAssign* compoundAssign) NonStrictContext visit(AstStatCompoundAssign* compoundAssign)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) visit(compoundAssign->var, ValueContext::LValue);
{ visit(compoundAssign->value, ValueContext::RValue);
visit(compoundAssign->var, ValueContext::LValue);
visit(compoundAssign->value, ValueContext::RValue);
}
return {}; return {};
} }
@ -556,13 +521,10 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstStatError* error) NonStrictContext visit(AstStatError* error)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) for (AstStat* stat : error->statements)
{ visit(stat);
for (AstStat* stat : error->statements) for (AstExpr* expr : error->expressions)
visit(stat); visit(expr, ValueContext::RValue);
for (AstExpr* expr : error->expressions)
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)
@ -650,17 +609,14 @@ 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.
{ if (context == ValueContext::LValue)
// We don't file unknown symbols for LValues. return {};
if (context == ValueContext::LValue)
return {};
NotNull<Scope> scope = stack.back(); NotNull<Scope> scope = stack.back();
if (!scope->lookup(global->name)) if (!scope->lookup(global->name))
{ {
reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location); reportError(UnknownSymbol{global->name.value, UnknownSymbol::Binding}, global->location);
}
} }
return {}; return {};
@ -783,24 +739,17 @@ 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 index = visit(indexExpr->index, ValueContext::RValue);
NonStrictContext expr = visit(indexExpr->expr, context); return NonStrictContext::disjunction(builtinTypes, arena, expr, index);
NonStrictContext index = visit(indexExpr->index, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, expr, index);
}
else
return {};
} }
NonStrictContext visit(AstExprFunction* exprFn) NonStrictContext visit(AstExprFunction* exprFn)
{ {
// TODO: should a function being used as an expression generate a context without the arguments? // TODO: should a function being used as an expression generate a context without the arguments?
@ -840,14 +789,11 @@ 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) if (key)
{ visit(key, ValueContext::RValue);
if (key) visit(value, ValueContext::RValue);
visit(key, ValueContext::RValue);
visit(value, ValueContext::RValue);
}
} }
return {}; return {};
@ -855,22 +801,14 @@ struct NonStrictTypeChecker
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 rhs = visit(binary->right, ValueContext::RValue);
NonStrictContext lhs = visit(binary->left, ValueContext::RValue); return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs);
NonStrictContext rhs = visit(binary->right, ValueContext::RValue);
return NonStrictContext::disjunction(builtinTypes, arena, lhs, rhs);
}
else
return {};
} }
NonStrictContext visit(AstExprTypeAssertion* typeAssertion) NonStrictContext visit(AstExprTypeAssertion* typeAssertion)
@ -878,10 +816,7 @@ struct NonStrictTypeChecker
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)
@ -894,22 +829,16 @@ struct NonStrictTypeChecker
NonStrictContext visit(AstExprInterpString* interpString) NonStrictContext visit(AstExprInterpString* interpString)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) for (AstExpr* expr : interpString->expressions)
{ visit(expr, ValueContext::RValue);
for (AstExpr* expr : interpString->expressions)
visit(expr, ValueContext::RValue);
}
return {}; return {};
} }
NonStrictContext visit(AstExprError* error) NonStrictContext visit(AstExprError* error)
{ {
if (FFlag::LuauNonStrictVisitorImprovements) for (AstExpr* expr : error->expressions)
{ visit(expr, ValueContext::RValue);
for (AstExpr* expr : error->expressions)
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
{ {
@ -2606,60 +2604,22 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
{ {
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;
// If any property is going to get mapped to `never`, we can just call the entire table `never`. // If any property is going to get mapped to `never`, we can just call the entire table `never`.
// Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties. // Since this check is syntactic, we may sometimes miss simplifying tables with complex uninhabited properties.
// Prior versions of this code attempted to do this semantically using the normalization machinery, but this // Prior versions of this code attempted to do this semantically using the normalization machinery, but this
// mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach // mistakenly causes infinite loops when giving more complex recursive table types. As it stands, this approach
// will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach // will continue to scale as simplification is improved, but we may wish to reintroduce the semantic approach
// once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize // once we have revisited the usage of seen sets systematically (and possibly with some additional guarding to recognize
// when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that // when types are infinitely-recursive with non-pointer identical instances of them, or some guard to prevent that
// construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization` // construction altogether). See also: `gh1632_no_infinite_recursion_in_normalization`
if (get<NeverType>(ty)) if (get<NeverType>(ty))
return {builtinTypes->neverType}; return {builtinTypes->neverType};
prop.readTy = ty; prop.readTy = ty;
hereSubThere &= (ty == hprop.readTy); hereSubThere &= (ty == hprop.readTy);
thereSubHere &= (ty == tprop.readTy); thereSubHere &= (ty == tprop.readTy);
}
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 else
{ {
@ -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
{ {
@ -57,25 +56,17 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log)
} }
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);
if (ty->persistent) if (ty->persistent)
return ty;
// While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`,
// we still assert above because we consider it a bug to have a non-persistent error type
// without any associated metadata. We should always use the persistent version in such cases.
ErrorType clone = ErrorType{};
clone.synthetic = a.synthetic;
return dest.addType(clone);
}
else
{
LUAU_ASSERT(ty->persistent);
return ty; return ty;
}
// While this code intentionally works (and clones) even if `a.synthetic` is `std::nullopt`,
// we still assert above because we consider it a bug to have a non-persistent error type
// without any associated metadata. We should always use the persistent version in such cases.
ErrorType clone = ErrorType{};
clone.synthetic = a.synthetic;
return dest.addType(clone);
} }
else if constexpr (std::is_same_v<T, UnknownType>) else if constexpr (std::is_same_v<T, UnknownType>)
{ {

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,12 +870,17 @@ 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".
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::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));
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;
}
TypeArena* arena = &module->internalTypes; auto [head, _] = extendTypePack(module->internalTypes, builtinTypes, expectedRetType, ret->list.size);
TypePackId actualRetType = reconstructPack(ret->list, *arena); 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]));
}
}
testIsSubtype(actualRetType, expectedRetType, ret->location); // 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;
TypePackId actualRetType = reconstructPack(ret->list, *arena);
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)
testIsSubtype(valueType, annotationType, value->location); {
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
testPotentialLiteralIsSubtype(value, annotationType);
else
testIsSubtype(valueType, annotationType, value->location);
}
visit(var->annotation); visit(var->annotation);
} }
@ -1154,14 +1223,26 @@ void TypeChecker2::visit(AstStatAssign* assign)
continue; continue;
} }
bool ok = testIsSubtype(rhsType, lhsType, rhs->location); if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
if (ok)
{ {
std::optional<TypeId> bindingType = getBindingType(lhs); // If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
if (bindingType) if (testPotentialLiteralIsSubtype(rhs, lhsType))
testIsSubtype(rhsType, *bindingType, rhs->location); {
if (std::optional<TypeId> bindingType = getBindingType(lhs))
testPotentialLiteralIsSubtype(rhs, *bindingType);
}
}
else
{
bool ok = testIsSubtype(rhsType, lhsType, rhs->location);
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
if (ok)
{
std::optional<TypeId> bindingType = getBindingType(lhs);
if (bindingType)
testIsSubtype(rhsType, *bindingType, rhs->location);
}
} }
} }
} }
@ -1200,14 +1281,19 @@ 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);
} }
void TypeChecker2::visit(AstTypeList types) void TypeChecker2::visit(AstTypeList types)
@ -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);
@ -674,114 +609,56 @@ 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;
try
{ {
InstanceCollector collector; collector.traverse(entrypoint);
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
);
} }
else catch (RecursionLimitException&)
{ {
InstanceCollector_DEPRECATED collector; return FunctionGraphReductionResult{};
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
);
} }
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;
try
{ {
InstanceCollector collector; collector.traverse(entrypoint);
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
);
} }
else catch (RecursionLimitException&)
{ {
InstanceCollector_DEPRECATED collector; return FunctionGraphReductionResult{};
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
);
} }
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,36 +811,22 @@ 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)
check.traverse(follow(typeParam)); check.traverse(follow(typeParam));
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))
@ -1180,12 +1043,9 @@ 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 (get<MetatableType>(normalizedOperand))
// If we have a metatable type with no __len, this means we still have a table with default length function return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
if (get<MetatableType>(normalizedOperand))
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,15 +2185,12 @@ 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) SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
{ return {result.result, {}};
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
return {result.result, {}};
}
} }
} }
@ -2620,17 +2477,9 @@ 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()}, {}};
}
}
else
{
if (!result.blockedTypes.empty())
return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}}; return {std::nullopt, Reduction::MaybeOk, {result.blockedTypes.begin(), result.blockedTypes.end()}, {}};
} }
@ -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,16 +2789,13 @@ 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 (tfit->function.get() == &builtinTypeFunctions().indexFunc)
// 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 indexType = follow(tblIndexer->indexResultType);
if (tfit->function.get() == &builtinTypeFunctions().indexFunc)
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,50 +2836,47 @@ 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;
for (auto component : unionTy)
{ {
bool res = true; // if the component is in the seen set and isn't the indexee itself,
for (auto component : unionTy) // we can skip it cause it means we encountered it in an earlier component in the union.
{ if (seenSet.contains(component) && component != indexee)
// if the component is in the seen set and isn't the indexee itself, continue;
// we can skip it cause it means we encountered it in an earlier component in the union.
if (seenSet.contains(component) && component != indexee)
continue;
res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw); res = res && tblIndexInto(indexer, component, result, seenSet, ctx, isRaw);
}
return res;
} }
return res;
}
if (get<FunctionType>(indexee)) if (get<FunctionType>(indexee))
{ {
TypePackId argPack = ctx->arena->addTypePack({indexer}); TypePackId argPack = ctx->arena->addTypePack({indexer});
SolveResult solveResult = solveFunctionCall( SolveResult solveResult = solveFunctionCall(
ctx->arena, ctx->arena,
ctx->builtins, ctx->builtins,
ctx->simplifier, ctx->simplifier,
ctx->normalizer, ctx->normalizer,
ctx->typeFunctionRuntime, ctx->typeFunctionRuntime,
ctx->ice, ctx->ice,
ctx->limits, ctx->limits,
ctx->scope, ctx->scope,
ctx->scope->location, ctx->scope->location,
indexee, indexee,
argPack argPack
); );
if (!solveResult.typePackId.has_value()) if (!solveResult.typePackId.has_value())
return false; return false;
TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1); TypePack extracted = extendTypePack(*ctx->arena, ctx->builtins, *solveResult.typePackId, 1);
if (extracted.head.empty()) if (extracted.head.empty())
return false; return false;
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
@ -3116,15 +2913,8 @@ 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{{}};
{ return tblIndexInto(indexer, indexee, result, seenSet, ctx, isRaw);
DenseHashSet<TypeId> seenSet{{}};
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,
@ -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 (indexeeNormTy->shouldSuppressErrors())
// if the indexee is `any`, then indexing also gives us `any`. return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}};
if (indexeeNormTy->shouldSuppressErrors())
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,14 +3412,11 @@ 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)
{ scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults) else
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc); scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
else scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
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,16 +435,9 @@ 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.
{ c2->readParent = parent;
// we don't yet have read/write parents in the type inference engine. c2->writeParent = parent;
c2->readParent = parent;
c2->writeParent = parent;
}
else
{
c2->parent_DEPRECATED = parent;
}
} }
} }

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,23 +1318,15 @@ 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());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
}
else
{
argPack = addTypePack(TypePack{});
}
}
else
{ {
std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end()); std::vector<TypeId> argTypes = std::vector<TypeId>(types.begin() + 1, types.end());
argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}}); argPack = addTypePack(TypePackVar{TypePack{std::move(argTypes), tail}});
} }
else
{
argPack = addTypePack(TypePack{});
}
} }
else else
{ {

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::Success;
else if (status == NavigationStatus::Ambiguous)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous; return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
else
if (result.status == PathResult::Status::NOT_FOUND)
return Luau::Require::NavigationContext::NavigateResult::NotFound; return Luau::Require::NavigationContext::NavigateResult::NotFound;
path = result.absPath;
suffix = result.suffix;
return Luau::Require::NavigationContext::NavigateResult::Success;
} }
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_SUCCESS;
else if (status == NavigationStatus::Ambiguous)
return NAVIGATE_AMBIGUOUS; return NAVIGATE_AMBIGUOUS;
else
if (result.status == PathResult::Status::NOT_FOUND)
return NAVIGATE_NOT_FOUND; return NAVIGATE_NOT_FOUND;
req->absPath = result.absPath;
req->relPath = result.relPath;
req->suffix = result.suffix;
return NAVIGATE_SUCCESS;
} }
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;
// 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
std::string bytecode = Luau::compile(*contents, req->copts());
status = luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0);
}
}
if (!hadContents)
luaL_error(L, "could not read file '%s'", loadname); luaL_error(L, "could not read file '%s'", loadname);
// now we can compile & run module on the new thread if (status == 0)
std::string bytecode = Luau::compile(*contents, req->copts());
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 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,25 +169,37 @@ 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())
{ {
std::optional<std::string> configContents = navigationContext.getConfig(); if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias)
if (!configContents) {
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; foundAliasValue = navigationContext.getAlias(desiredAlias);
}
else
{
std::optional<std::string> configContents = navigationContext.getConfig();
if (!configContents)
return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\"";
Luau::ConfigOptions opts; Luau::ConfigOptions opts;
Luau::ConfigOptions::AliasOptions aliasOpts; Luau::ConfigOptions::AliasOptions aliasOpts;
aliasOpts.configLocation = "unused"; aliasOpts.configLocation = "unused";
aliasOpts.overwriteAliases = false; aliasOpts.overwriteAliases = false;
opts.aliasOptions = std::move(aliasOpts); opts.aliasOptions = std::move(aliasOpts);
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 resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); // ResolvedRequire will be destroyed and any string will be pinned to Luau stack, so that luaL_error doesn't need destructors
if (resolvedRequire.status == ResolvedRequire::Status::Cached) bool resolveError = false;
return 1;
// (1) path, ..., cacheKey {
lua_pushstring(L, resolvedRequire.cacheKey.c_str()); ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path);
// Insert number of arguments before the continuation to check the results. if (resolvedRequire.status == ResolvedRequire::Status::Cached)
int numArgsBeforeLoad = stackTop + 2; return 1;
lua_pushinteger(L, numArgsBeforeLoad);
lua_insert(L, 1);
int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.loadname.c_str()); 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.chunkname.c_str());
lua_pushstring(L, resolvedRequire.loadname.c_str());
}
}
if (resolveError)
lua_error(L); // Error already on top of the stack
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)
{ {
return pcRel(ci->savedpc, ci_func(ci)->l.p); if (FFlag::LuauCurrentLineBounds)
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 data[1]; char padding[sizeof(void*) == 8 ? 8 : 12];
double align1;
void* align2; char data[1];
};
}; };
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,26 +149,53 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
L->base = L->ci->base; L->base = L->ci->base;
} }
// note: the pc expectations of the hook are matching the general "pc points to next instruction" if (FFlag::LuauCurrentLineBounds)
// 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 Closure* cl = clvalue(L->ci->func);
if (L->ci->savedpc)
L->ci->savedpc++;
luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size // note: the pc expectations of the hook are matching the general "pc points to next instruction"
L->ci->top = L->top + LUA_MINSTACK; // however, for the hook to be able to continue execution from the same point, this is called with savedpc at the *current* instruction
LUAU_ASSERT(L->ci->top <= L->stack_last); // this needs to be called before luaD_checkstack in case it fails to reallocate stack
const Instruction* oldsavedpc = L->ci->savedpc;
Closure* cl = clvalue(L->ci->func); if (L->ci->savedpc && L->ci->savedpc != cl->l.p->code + cl->l.p->sizecode)
L->ci->savedpc++;
lua_Debug ar; luaD_checkstack(L, LUA_MINSTACK); // ensure minimum stack size
ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p)); L->ci->top = L->top + LUA_MINSTACK;
ar.userdata = userdata; LUAU_ASSERT(L->ci->top <= L->stack_last);
hook(L, &ar); lua_Debug ar;
ar.currentline = cl->isC ? -1 : luaG_getline(cl->l.p, pcRel(L->ci->savedpc, cl->l.p));
ar.userdata = userdata;
if (L->ci->savedpc) hook(L, &ar);
L->ci->savedpc--;
L->ci->savedpc = oldsavedpc;
}
else
{
// 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
if (L->ci->savedpc)
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);
Closure* cl = clvalue(L->ci->func);
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);
if (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,7 +46,16 @@ struct TempBuffer
~TempBuffer() noexcept ~TempBuffer() noexcept
{ {
luaM_freearray(L, data, count, T, 0); if (data)
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)
@ -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)
CHECK(ac.entryMap.empty()); {
// 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());
}
} }
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);
} }
@ -3219,7 +3375,7 @@ TEST_CASE("NativeAttribute")
local function subHelper(z) local function subHelper(z)
return (x+y-z) return (x+y-z)
end end
return subHelper return subHelper
end)R"; end)R";
StateRef globalState(luaL_newstate(), lua_close); StateRef globalState(luaL_newstate(), lua_close);

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