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
{
static constexpr char kRequireTagName[] = "require";
inline constexpr char kRequireTagName[] = "require";
struct Frontend;
struct GlobalTypes;

View file

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

View file

@ -173,6 +173,8 @@ private:
std::vector<std::vector<TypeId>> DEPRECATED_interiorTypes;
std::vector<InteriorFreeTypes> interiorFreeTypes;
std::vector<TypeId> unionsToSimplify;
/**
* Fabricates a new free type belonging to a given scope.
* @param scope the scope the free type belongs to.
@ -447,6 +449,12 @@ private:
// make a union type function of these two types
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
TypeId makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs);
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 EqualityConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SimplifyConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do
// also handles __iter metamethod
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;
};
struct UnexpectedArrayLikeTableItem
{
bool operator==(const UnexpectedArrayLikeTableItem&) const { return true; }
};
using TypeErrorData = Variant<
TypeMismatch,
UnknownSymbol,
@ -512,7 +517,8 @@ using TypeErrorData = Variant<
UnexpectedTypePackInSubtyping,
ExplicitFunctionAnnotationRecommended,
UserDefinedTypeFunctionError,
ReservedIdentifier>;
ReservedIdentifier,
UnexpectedArrayLikeTableItem>;
struct TypeErrorSummary
{

View file

@ -8,7 +8,7 @@
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::CompareNe, "__eq"},
{AstExprBinary::Op::CompareGe, "__lt"},
@ -25,7 +25,7 @@ static const std::unordered_map<AstExprBinary::Op, const char*> kBinaryOpMetamet
{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::Len, "__len"},
};

View file

@ -60,7 +60,7 @@ struct 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
{

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`.
// 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(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(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(TypePackId subTy, TypePackId superTy, Location location);
void reportError(TypeError e);

View file

@ -216,9 +216,6 @@ struct TypeFunctionExternType
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> writeParent;

View file

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

View file

@ -291,4 +291,14 @@ void trackInteriorFreeType(Scope* scope, TypeId ty);
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

View file

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

View file

@ -30,9 +30,8 @@
*/
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
@ -292,8 +291,6 @@ void assignPropDocumentationSymbols(TableType::Props& props, const std::string&
static void finalizeGlobalBindings(ScopePtr scope)
{
LUAU_ASSERT(FFlag::LuauUserTypeFunTypecheck);
for (const auto& pair : scope->bindings)
{
persist(pair.second.typeId);
@ -313,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization2
if (FFlag::LuauNonReentrantGeneralization2)
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauNonReentrantGeneralization3
if (FFlag::LuauNonReentrantGeneralization3)
globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2)
@ -420,23 +417,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
// clang-format on
}
if (FFlag::LuauUserTypeFunTypecheck)
{
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>());
@ -500,8 +481,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
attachTag(requireTy, kRequireTagName);
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;
@ -552,7 +531,6 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
finalizeGlobalBindings(globals.globalTypeFunctionScope);
}
}
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);
// `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))
{
rci.traverse(rpc->tp);

View file

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

View file

@ -35,11 +35,14 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization)
LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion)
LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
namespace Luau
{
@ -686,6 +689,13 @@ void ConstraintSolver::initFreeTypeTracking()
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)
@ -729,7 +739,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity?
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeType(constraint->scope, ty);
return;
@ -819,6 +829,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*rpc, constraint, force);
else if (auto eqc = get<EqualityConstraint>(*constraint))
success = tryDispatch(*eqc, constraint);
else if (auto sc = get<SimplifyConstraint>(*constraint))
success = tryDispatch(*sc, constraint);
else
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)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
ty = follow(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)
{
@ -1519,7 +1531,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy);
@ -1540,6 +1552,51 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (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)
@ -1683,12 +1740,32 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
(*c.astExpectedTypes)[expr] = expectedArgTy;
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
// Generic types are skipped over entirely, for now.
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;
}
const FunctionType* expectedLambdaTy = get<FunctionType>(expectedArgTy);
const FunctionType* lambdaTy = get<FunctionType>(actualArgTy);
const AstExprFunction* lambdaExpr = expr->as<AstExprFunction>();
if (expectedLambdaTy && lambdaTy && lambdaExpr)
@ -1899,7 +1976,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType};
@ -1980,8 +2057,9 @@ bool ConstraintSolver::tryDispatchHasIndexer(
continue;
r = follow(r);
if (!get<ErrorType>(r))
if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r))
results.insert(r);
}
if (0 == results.size())
@ -2443,18 +2521,15 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
for (TypePackId r : result.reducedPacks)
unblock(r, constraint->location);
if (FFlag::LuauNewTypeFunReductionChecks2)
{
for (TypeId ity : result.irreducibleTypes)
uninhabitedTypeFunctions.insert(ity);
}
bool reductionFinished = result.blockedTypes.empty() && result.blockedPacks.empty();
ty = follow(ty);
// 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;
if (force || reductionFinished)
@ -2531,6 +2606,83 @@ bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Co
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)
{
iteratorTy = follow(iteratorTy);

View file

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

View file

@ -7,7 +7,7 @@ LUAU_FASTFLAG(LuauTypeFunOptional)
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
@ -60,7 +60,7 @@ declare function unpack<V>(tab: {V}, i: number?, j: number?): ...V
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionBit32Src = R"BUILTIN_SRC(
declare bit32: {
band: @checked (...number) -> number,
@ -82,7 +82,7 @@ declare bit32: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionMathSrc = R"BUILTIN_SRC(
declare math: {
frexp: @checked (n: number) -> (number, number),
@ -133,7 +133,7 @@ declare math: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionOsSrc = R"BUILTIN_SRC(
type DateTypeArg = {
year: number,
@ -166,7 +166,7 @@ declare os: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC(
declare coroutine: {
create: <A..., R...>(f: (A...) -> R...) -> thread,
@ -181,7 +181,7 @@ declare coroutine: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionTableSrc = R"BUILTIN_SRC(
declare table: {
concat: <V>(t: {V}, sep: string?, i: number?, j: number?) -> string,
@ -207,7 +207,7 @@ declare table: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC(
declare debug: {
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";
static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC(
declare utf8: {
char: @checked (...number) -> string,
@ -229,7 +229,7 @@ declare utf8: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionBufferSrc = R"BUILTIN_SRC(
--- Buffer API
declare buffer: {
create: @checked (size: number) -> buffer,
@ -262,7 +262,7 @@ declare buffer: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
static const char* const kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType)
? 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
@ -340,7 +340,7 @@ std::string getBuiltinDefinitionSource()
}
// 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 = {
tag: "nil" | "unknown" | "never" | "any" | "boolean" | "number" | "string" | "buffer" | "thread" |
@ -393,7 +393,7 @@ export type type = {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionTypesLibSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,
@ -416,7 +416,7 @@ declare types: {
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
static constexpr const char* kBuiltinDefinitionTypesLibWithOptionalSrc = R"BUILTIN_SRC(
declare types: {
unknown: type,

View file

@ -839,6 +839,11 @@ struct ErrorConverter
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
@ -1432,6 +1437,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
for (auto& ty : e.cause)
ty = clone(ty);
}
else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>)
{
}
else if constexpr (std::is_same_v<T, ReservedIdentifier>)
{
}

View file

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

View file

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

View file

@ -16,7 +16,7 @@
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization3)
namespace Luau
{
@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
if (!subsumes(scope, ft.scope))
return true;
@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
unsealedTables.insert(ty);
else
{
@ -559,7 +559,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
// {[K]: V} is equivalent to three functions: get, set, and iterate
//
@ -617,7 +617,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope))
return true;
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount;
@ -1159,7 +1159,7 @@ struct RemoveType : Substitution // NOLINT
for (TypeId ty : ut)
{
if (ty != needle)
if (ty != needle && !is<NeverType>(ty))
newParts.insert(ty);
}
@ -1180,7 +1180,7 @@ struct RemoveType : Substitution // NOLINT
for (TypeId ty : it)
{
if (ty != needle)
if (ty != needle && !is<UnknownType>(ty))
newParts.insert(ty);
}
@ -1301,7 +1301,23 @@ GeneralizationResult<TypeId> generalizeType(
if (follow(ub) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), ub);
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
{
// 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};
fts.traverse(ty);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t)

View file

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

View file

@ -248,6 +248,8 @@ static void errorToString(std::ostream& stream, const T& err)
stream << " } } ";
}
else if constexpr (std::is_same_v<T, UnexpectedArrayLikeTableItem>)
stream << "UnexpectedArrayLikeTableItem {}";
else
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)
return nullptr;

View file

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

View file

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

View file

@ -11,6 +11,7 @@
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
namespace Luau
{
@ -287,6 +288,25 @@ std::pair<OverloadResolver::Analysis, ErrorVec> OverloadResolver::checkOverload_
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;

View file

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

View file

@ -17,12 +17,10 @@
#include "Luau/TypePath.h"
#include "Luau/TypeUtils.h"
#include <algorithm>
LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypingEnableReasoningLimit)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
namespace Luau
{
@ -101,7 +99,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
result.insert(r);
}
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
@ -120,7 +118,7 @@ static SubtypingReasonings mergeReasonings(const SubtypingReasonings& a, const S
result.insert(r);
}
if (FFlag::LuauSubtypingEnableReasoningLimit && result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
if (result.size() >= size_t(FInt::LuauSubtypingReasoningLimit))
return result;
}
@ -815,7 +813,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X>(X) -> () <: (T) -> ()
// 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);
if (TypePackId* other = env.getMappedPackBounds(*subTail))
@ -870,11 +870,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
// <X...>(X...) -> () <: (T) -> ()
// 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);
if (TypePackId* other = env.getMappedPackBounds(*superTail))
// TODO: TypePath can't express "slice of a pack + its tail".
if (FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2)
results.push_back(isCovariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
else
results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail));
else
env.mappedGenericPacks.try_insert(*superTail, subTailPack);

View file

@ -14,17 +14,11 @@
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
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)
{
if (item.kind == AstExprTable::Item::Record)
@ -35,80 +29,6 @@ static bool isRecord(const AstExprTable::Item& item)
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(
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
@ -257,7 +177,6 @@ TypeId matchLiteralType(
if (tt)
{
TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock);
parts.push_back(res);
return arena->addType(UnionType{std::move(parts)});
}

View file

@ -22,7 +22,6 @@
LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors)
LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit)
/*
@ -1092,7 +1091,7 @@ struct TypeStringifier
{
state.result.error = true;
if (FFlag::LuauSyntheticErrors && tv.synthetic)
if (tv.synthetic)
{
state.emit("*error-type<");
stringify(*tv.synthetic);
@ -1278,7 +1277,7 @@ struct TypePackStringifier
{
state.result.error = true;
if (FFlag::LuauSyntheticErrors && error.synthetic)
if (error.synthetic)
{
state.emit("*");
stringify(*error.synthetic);
@ -1965,6 +1964,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
else if constexpr (std::is_same_v<T, TableCheckConstraint>)
return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType);
else if constexpr (std::is_same_v<T, SimplifyConstraint>)
return "simplify " + tos(c.ty);
else
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>
LUAU_FASTFLAG(LuauStoreCSTData2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
@ -308,8 +307,7 @@ public:
std::optional<AstArgumentName>* arg = &argNames.data[i++];
if (el)
new (arg)
std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), FFlag::LuauStoreCSTData2 ? Location() : el->location));
new (arg) std::optional<AstArgumentName>(AstArgumentName(AstName(el->name.c_str()), Location()));
else
new (arg) std::optional<AstArgumentName>();
}

View file

@ -30,11 +30,12 @@
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau
{
@ -710,14 +711,77 @@ void TypeChecker2::visit(AstStatReturn* ret)
{
Scope* scope = findInnermostScope(ret->location);
TypePackId expectedRetType = scope->returnType;
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
{
if (ret->list.size == 0)
{
testIsSubtype(builtinTypes->emptyTypePack, expectedRetType, ret->location);
return;
}
auto [head, _] = extendTypePack(module->internalTypes, builtinTypes, expectedRetType, ret->list.size);
bool isSubtype = true;
std::vector<TypeId> actualHead;
std::optional<TypePackId> actualTail;
for (size_t idx = 0; idx < ret->list.size - 1; idx++)
{
if (idx < head.size())
{
isSubtype &= testPotentialLiteralIsSubtype(ret->list.data[idx], head[idx]);
actualHead.push_back(head[idx]);
}
else
{
actualHead.push_back(lookupType(ret->list.data[idx]));
}
}
// This stanza is deconstructing what constraint generation does to
// return statements. If we have some statement like:
//
// return E0, E1, E2, ... , EN
//
// All expressions *except* the last will be types, and the last can
// potentially be a pack. However, if the last expression is a function
// call or varargs (`...`), then we _could_ have a pack in the final
// position. Additionally, if we have an argument overflow, then we can't
// do anything interesting with subtyping.
//
// _If_ the last argument is not a function call or varargs and we have
// at least an argument underflow, then we grab the last type out of
// the type pack head and use that to check the subtype of
auto lastExpr = ret->list.data[ret->list.size - 1];
if (head.size() < ret->list.size || lastExpr->is<AstExprCall>() || lastExpr->is<AstExprVarargs>())
{
actualTail = lookupPack(lastExpr);
}
else
{
auto lastType = head[ret->list.size - 1];
isSubtype &= testPotentialLiteralIsSubtype(lastExpr, lastType);
actualHead.push_back(lastType);
}
// After all that, we still fire a pack subtype test to determine
// whether we have a well-formed return statement. We only fire
// this if all the previous subtype tests have succeeded, lest
// we double error.
if (isSubtype)
{
auto reconstructedRetType = module->internalTypes.addTypePack(TypePack{std::move(actualHead), std::move(actualTail)});
testIsSubtype(reconstructedRetType, expectedRetType, ret->location);
}
}
else
{
TypeArena* arena = &module->internalTypes;
TypePackId actualRetType = reconstructPack(ret->list, *arena);
testIsSubtype(actualRetType, expectedRetType, ret->location);
}
for (AstExpr* expr : ret->list)
visit(expr, ValueContext::RValue);
}
void TypeChecker2::visit(AstStatExpr* expr)
@ -745,7 +809,12 @@ void TypeChecker2::visit(AstStatLocal* local)
TypeId annotationType = lookupAnnotation(var->annotation);
TypeId valueType = value ? lookupType(value) : nullptr;
if (valueType)
{
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
testPotentialLiteralIsSubtype(value, annotationType);
else
testIsSubtype(valueType, annotationType, value->location);
}
visit(var->annotation);
}
@ -1154,6 +1223,17 @@ void TypeChecker2::visit(AstStatAssign* assign)
continue;
}
if (FFlag::LuauTableLiteralSubtypeSpecificCheck)
{
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
if (testPotentialLiteralIsSubtype(rhs, lhsType))
{
if (std::optional<TypeId> bindingType = getBindingType(lhs))
testPotentialLiteralIsSubtype(rhs, *bindingType);
}
}
else
{
bool ok = testIsSubtype(rhsType, lhsType, rhs->location);
// If rhsType </: lhsType, then it's not useful to also report that rhsType </: bindingType
@ -1165,6 +1245,7 @@ void TypeChecker2::visit(AstStatAssign* assign)
}
}
}
}
void TypeChecker2::visit(AstStatCompoundAssign* stat)
{
@ -1200,13 +1281,18 @@ void TypeChecker2::visit(const AstTypeList* typeList)
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);
visit(stat->type);
}
void TypeChecker2::visit(AstStatTypeFunction* stat)
{
if (FFlag::LuauUserTypeFunTypecheck)
visit(stat->body);
}
@ -2857,6 +2943,130 @@ void TypeChecker2::explainError(TypePackId subTy, TypePackId superTy, Location l
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)
{
NotNull<Scope> scope{findInnermostScope(location)};

View file

@ -47,24 +47,14 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
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_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
namespace Luau
@ -72,61 +62,6 @@ namespace Luau
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
{
DenseHashSet<TypeId> recordedTys{nullptr};
@ -546,7 +481,7 @@ struct TypeFunctionReducer
if (const TypeFunctionInstanceType* tfit = get<TypeFunctionInstanceType>(subject))
{
if (FFlag::LuauNewTypeFunReductionChecks2 && tfit->function->name == "user")
if (tfit->function->name == "user")
{
UnscopedGenericFinder finder;
finder.traverse(subject);
@ -673,8 +608,6 @@ static FunctionGraphReductionResult reduceFunctionsInternal(
}
FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{
if (FFlag::LuauNewTypeFunReductionChecks2)
{
InstanceCollector collector;
@ -700,37 +633,8 @@ FunctionGraphReductionResult reduceTypeFunctions(TypeId entrypoint, Location loc
force
);
}
else
{
InstanceCollector_DEPRECATED collector;
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
}
}
FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location location, TypeFunctionContext ctx, bool force)
{
if (FFlag::LuauNewTypeFunReductionChecks2)
{
InstanceCollector collector;
@ -756,33 +660,6 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location
force
);
}
else
{
InstanceCollector_DEPRECATED collector;
try
{
collector.traverse(entrypoint);
}
catch (RecursionLimitException&)
{
return FunctionGraphReductionResult{};
}
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFunctionsInternal(
std::move(collector.tys),
std::move(collector.tps),
std::move(collector.shouldGuess),
std::move(collector.cyclicInstance),
location,
ctx,
force
);
}
}
bool isPending(TypeId ty, ConstraintSolver* solver)
{
@ -934,11 +811,9 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
}
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
if (!ctx->typeFunctionRuntime->allowEvaluation || (FFlag::LuauTypeFunResultInAutocomplete && typeFunction->userFuncData.definition->hasErrors))
if (!ctx->typeFunctionRuntime->allowEvaluation || typeFunction->userFuncData.definition->hasErrors)
return {ctx->builtins->errorRecoveryType(), Reduction::MaybeOk, {}, {}};
if (FFlag::LuauNewTypeFunReductionChecks2)
{
FindUserTypeFunctionBlockers check{ctx};
for (auto typeParam : typeParams)
@ -946,24 +821,12 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
if (!check.blockingTypes.empty())
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
for (auto& [name, definition] : typeFunction->userFuncData.environment)
{
// 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, {}, {}};
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
@ -1179,13 +1042,10 @@ TypeFunctionReductionResult<TypeId> lenTypeFunction(
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, operandTy, "__len", Location{});
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))
return {ctx->builtins->numberType, Reduction::MaybeOk, {}, {}};
}
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
@ -1241,7 +1101,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
operandTy = follow(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;
// Do not evaluate type functions with parse errors inside
if (FFlag::LuauTypeFunResultInAutocomplete && function->hasErrors)
if (function->hasErrors)
return std::nullopt;
prepareState();
@ -1918,12 +1778,12 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
emplaceType<BoundType>(asMutable(lhsTy), ctx->builtins->numberType);
else if (rhsFree && isNumber(lhsTy))
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});
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});
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
@ -2325,8 +2185,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (!crt.found)
return {target, {}};
if (FFlag::LuauSimplyRefineNotNil)
{
if (auto negation = get<NegationType>(discriminant))
{
if (auto primitive = get<PrimitiveType>(follow(negation->ty)); primitive && primitive->type == PrimitiveType::NilType)
@ -2335,7 +2193,6 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
return {result.result, {}};
}
}
}
if (FFlag::LuauOptimizeFalsyAndTruthyIntersect)
{
@ -2620,19 +2477,11 @@ TypeFunctionReductionResult<TypeId> intersectTypeFunction(
}
}
if (FFlag::LuauIntersectNotNil)
{
for (TypeId blockedType : result.blockedTypes)
{
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()}, {}};
}
resultTy = result.result;
}
@ -2858,7 +2707,7 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
std::vector<TypeId> singletons;
singletons.reserve(keys.size());
for (std::string key : keys)
for (const std::string& key : keys)
singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}}));
// If there's only one entry, we don't need a UnionType.
@ -2927,10 +2776,7 @@ bool searchPropsAndIndexer(
{
for (TypeId option : propUnionTy->options)
{
if (FFlag::LuauIndexTypeFunctionImprovements)
result.insert(follow(option));
else
result.insert(option);
}
}
else // property is a singular type or intersection type -> we can simply append
@ -2943,17 +2789,14 @@ bool searchPropsAndIndexer(
// index into tbl's indexer
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 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)
indexType = follow(tblIndexer->indexResultType);
}
}
if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice))
{
@ -2964,10 +2807,7 @@ bool searchPropsAndIndexer(
{
for (TypeId option : idxResUnionTy->options)
{
if (FFlag::LuauIndexTypeFunctionImprovements)
result.insert(follow(option));
else
result.insert(option);
}
}
else // indexResultType is a singular type or intersection type -> we can simply append
@ -2980,46 +2820,6 @@ bool searchPropsAndIndexer(
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(
TypeId indexer,
TypeId indexee,
@ -3036,8 +2836,6 @@ bool tblIndexInto(
return false;
seenSet.insert(indexee);
if (FFlag::LuauIndexTypeFunctionFunctionMetamethods)
{
if (auto unionTy = get<UnionType>(indexee))
{
bool res = true;
@ -3080,7 +2878,6 @@ bool tblIndexInto(
result.insert(follow(extracted.head.front()));
return true;
}
}
// we have a table type to try indexing
if (auto tableTy = get<TableType>(indexee))
@ -3115,17 +2912,10 @@ bool tblIndexInto(
}
bool tblIndexInto(TypeId indexer, TypeId indexee, DenseHashSet<TypeId>& result, NotNull<TypeFunctionContext> ctx, bool isRaw)
{
if (FFlag::LuauIndexTypeFunctionImprovements)
{
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,
indexer refers to the type that is used to access indexee
@ -3139,7 +2929,7 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
{
TypeId indexeeTy = follow(typeParams.at(0));
if (FFlag::LuauIndexDeferPendingIndexee && isPending(indexeeTy, ctx->solver))
if (isPending(indexeeTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {indexeeTy}, {}};
std::shared_ptr<const NormalizedType> indexeeNormTy = ctx->normalizer->normalize(indexeeTy);
@ -3148,12 +2938,9 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
if (!indexeeNormTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}};
if (FFlag::LuauIndexAnyIsAny)
{
// if the indexee is `any`, then indexing also gives us `any`.
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 (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 (properties.size() == 1)
return {*properties.begin(), Reduction::MaybeOk, {}, {}};
@ -3544,7 +3317,6 @@ TypeFunctionReductionResult<TypeId> weakoptionalTypeFunc(
return {targetTy, Reduction::MaybeOk, {}, {}};
}
BuiltinTypeFunctions::BuiltinTypeFunctions()
: userFunc{"user", userDefinedTypeFunction}
, notFunc{"not", notTypeFunction}
@ -3640,15 +3412,12 @@ void BuiltinTypeFunctions::addToScope(NotNull<TypeArena> arena, NotNull<Scope> s
scope->exportedTypeBindings[rawgetFunc.name] = mkBinaryTypeFunctionWithDefault(&rawgetFunc);
}
if (FFlag::LuauMetatableTypeFunctions)
{
if (FFlag::LuauNotAllBinaryTypeFunsHaveDefaults)
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunction(&setmetatableFunc);
else
scope->exportedTypeBindings[setmetatableFunc.name] = mkBinaryTypeFunctionWithDefault(&setmetatableFunc);
scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc);
}
}
const BuiltinTypeFunctions& builtinTypeFunctions()
{

View file

@ -14,7 +14,6 @@
#include <vector>
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauTypeFunReadWriteParents)
LUAU_FASTFLAGVARIABLE(LuauTypeFunOptional)
namespace Luau
@ -1138,28 +1137,6 @@ static int getFunctionGenerics(lua_State* L)
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`
// Returns the read type of the class' parent
static int getReadParent(lua_State* L)
@ -1628,7 +1605,8 @@ void registerTypeUserData(lua_State* L)
{"components", getComponents},
// Extern type methods
{FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED},
{"readparent", getReadParent},
{"writeparent", getWriteParent},
// Function type methods (cont.)
{"setgenerics", setFunctionGenerics},
@ -1638,9 +1616,6 @@ void registerTypeUserData(lua_State* L)
{"name", getGenericName},
{"ispack", getGenericIsPack},
// move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents
{FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr},
{nullptr, nullptr}
};

View file

@ -19,7 +19,6 @@
// used to control the recursion limit of any operations done by user-defined type functions
// currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauTypeFunReadWriteParents)
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
// class
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))
@ -436,17 +435,10 @@ private:
{
TypeFunctionTypeId parent = shallowSerialize(*c1->parent);
if (FFlag::LuauTypeFunReadWriteParents)
{
// we don't yet have read/write parents in the type inference engine.
c2->readParent = parent;
c2->writeParent = parent;
}
else
{
c2->parent_DEPRECATED = parent;
}
}
}
void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2)

View file

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

View file

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

View file

@ -4,15 +4,17 @@
#include "Luau/Common.h"
#include "Luau/Normalize.h"
#include "Luau/Scope.h"
#include "Luau/Simplify.h"
#include "Luau/ToString.h"
#include "Luau/Type.h"
#include "Luau/TypeInfer.h"
#include <algorithm>
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
namespace Luau
{
@ -305,7 +307,7 @@ TypePack extendTypePack(
TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2)
@ -434,6 +436,25 @@ TypeId stripNil(NotNull<BuiltinTypes> builtinTypes, TypeArena& arena, 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);
if (!normType)
@ -570,7 +591,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{
LUAU_ASSERT(tp);
if (!FFlag::LuauNonReentrantGeneralization2)
if (!FFlag::LuauNonReentrantGeneralization3)
return;
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.");
}
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

View file

@ -18,7 +18,7 @@
#include <optional>
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauGreedyGeneralization)
namespace Luau
@ -329,7 +329,7 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
for (TypePackId genericPack : subFn->genericPacks)
{
if (FFlag::LuauNonReentrantGeneralization2)
if (FFlag::LuauNonReentrantGeneralization3)
{
if (FFlag::DebugLuauGreedyGeneralization)
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->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)

View file

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

View file

@ -245,8 +245,6 @@ private:
};
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);
AstType* parseFunctionTypeTail(

File diff suppressed because it is too large Load diff

View file

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

View file

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

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
#include "Luau/AnalyzeRequirer.h"
#include "Luau/FileUtils.h"
#include "Luau/RequireNavigator.h"
#include "Luau/RequirerUtils.h"
#include "Luau/VfsNavigator.h"
#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)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
if (result.status == PathResult::Status::NOT_FOUND)
return Luau::Require::NavigationContext::NavigateResult::NotFound;
path = result.absPath;
suffix = result.suffix;
if (status == NavigationStatus::Success)
return Luau::Require::NavigationContext::NavigateResult::Success;
else if (status == NavigationStatus::Ambiguous)
return Luau::Require::NavigationContext::NavigateResult::Ambiguous;
else
return Luau::Require::NavigationContext::NavigateResult::NotFound;
}
FileNavigationContext::FileNavigationContext(std::string requirerPath)
: 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
@ -49,51 +27,58 @@ std::string FileNavigationContext::getRequirerIdentifier() const
return requirerPath;
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& requirerChunkname)
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::reset(const std::string& identifier)
{
if (requirerChunkname == "-")
{
return storePathResult(getStdInResult());
}
if (identifier == "-")
return convert(vfs.resetToStdIn());
return storePathResult(tryGetRelativePathResult(requirerChunkname));
return convert(vfs.resetToPath(identifier));
}
Luau::Require::NavigationContext::NavigateResult FileNavigationContext::jumpToAlias(const std::string& path)
{
Luau::Require::NavigationContext::NavigateResult result = storePathResult(getAbsolutePathResult(path));
if (result != Luau::Require::NavigationContext::NavigateResult::Success)
return result;
if (!isAbsolutePath(path))
return Luau::Require::NavigationContext::NavigateResult::NotFound;
return Luau::Require::NavigationContext::NavigateResult::Success;
return convert(vfs.resetToPath(path));
}
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)
{
return storePathResult(getChild(path, path, component));
return convert(vfs.toChild(component));
}
bool FileNavigationContext::isModulePresent() const
{
return isFilePresent(path, suffix);
return isFile(vfs.getAbsoluteFilePath());
}
std::optional<std::string> FileNavigationContext::getIdentifier() const
{
return path + suffix;
return vfs.getAbsoluteFilePath();
}
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
{
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
luaL_sandboxthread(L);
// ignore file extension when storing module's chunkname
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 chunkname = "@" + normalizePath(name);
std::string bytecode = Luau::compile(*source, copts());
int status = 0;

View file

@ -5,8 +5,8 @@
#include "Luau/CodeGenOptions.h"
#include "Luau/FileUtils.h"
#include "Luau/Require.h"
#include "Luau/VfsNavigator.h"
#include "Luau/RequirerUtils.h"
#include "lua.h"
#include "lualib.h"
@ -32,19 +32,14 @@ static luarequire_WriteResult write(std::optional<std::string> contents, char* b
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)
return NAVIGATE_AMBIGUOUS;
if (result.status == PathResult::Status::NOT_FOUND)
return NAVIGATE_NOT_FOUND;
req->absPath = result.absPath;
req->relPath = result.relPath;
req->suffix = result.suffix;
if (status == NavigationStatus::Success)
return NAVIGATE_SUCCESS;
else if (status == NavigationStatus::Ambiguous)
return NAVIGATE_AMBIGUOUS;
else
return NAVIGATE_NOT_FOUND;
}
static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname)
@ -59,13 +54,9 @@ static luarequire_NavigateResult reset(lua_State* L, void* ctx, const char* requ
std::string chunkname = requirer_chunkname;
if (chunkname == "=stdin")
{
return storePathResult(req, getStdInResult());
}
return convert(req->vfs.resetToStdIn());
else if (!chunkname.empty() && chunkname[0] == '@')
{
return storePathResult(req, tryGetRelativePathResult(chunkname.substr(1)));
}
return convert(req->vfs.resetToPath(chunkname.substr(1)));
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);
luarequire_NavigateResult result = storePathResult(req, getAbsolutePathResult(path));
if (result != NAVIGATE_SUCCESS)
return result;
if (!isAbsolutePath(path))
return NAVIGATE_NOT_FOUND;
// Jumping to an absolute path breaks the relative-require chain. The best
// we can do is to store the absolute path itself.
req->relPath = req->absPath;
return NAVIGATE_SUCCESS;
return convert(req->vfs.resetToPath(path));
}
static luarequire_NavigateResult to_parent(lua_State* L, void* 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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
@ -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
luaL_sandboxthread(ML);
std::optional<std::string> contents = readFile(loadname);
if (!contents)
luaL_error(L, "could not read file '%s'", loadname);
bool hadContents = false;
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());
if (luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0) == 0)
status = luau_load(ML, chunkname, bytecode.data(), bytecode.size(), 0);
}
}
if (!hadContents)
luaL_error(L, "could not read file '%s'", loadname);
if (status == 0)
{
if (req->codegenEnabled())
{
@ -208,6 +208,7 @@ void requireConfigInit(luarequire_Configuration* config)
config->get_chunkname = get_chunkname;
config->get_loadname = get_loadname;
config->get_cache_key = get_cache_key;
config->get_alias = nullptr;
config->get_config = get_config;
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
// 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
{
@ -900,7 +900,7 @@ struct IrInst
};
// 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
{

View file

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

View file

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

View file

@ -27,26 +27,26 @@ namespace A64
// Data that is very common to access is placed in non-volatile registers:
// 1. Constant registers (only loaded during codegen entry)
constexpr RegisterA64 rState = x19; // lua_State* L
constexpr RegisterA64 rNativeContext = x20; // NativeContext* context
constexpr RegisterA64 rGlobalState = x21; // global_State* L->global
inline constexpr RegisterA64 rState = x19; // lua_State* L
inline constexpr RegisterA64 rNativeContext = x20; // NativeContext* context
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)
constexpr RegisterA64 rConstants = x22; // TValue* k
constexpr RegisterA64 rClosure = x23; // Closure* cl
constexpr RegisterA64 rCode = x24; // Instruction* code
constexpr RegisterA64 rBase = x25; // StkId base
inline constexpr RegisterA64 rConstants = x22; // TValue* k
inline constexpr RegisterA64 rClosure = x23; // Closure* cl
inline constexpr RegisterA64 rCode = x24; // Instruction* code
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
// See CodeGenA64.cpp for layout
constexpr unsigned kStashSlots = 9; // stashed non-volatile registers
constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury!
constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers
inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers
inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury!
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);
constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8);
inline constexpr AddressA64 sSpillArea = mem(sp, (kStashSlots + kTempSlots) * 8);
inline constexpr AddressA64 sTemporary = mem(sp, kStashSlots * 8);
inline void emitUpdateBase(AssemblyBuilderA64& build)
{

View file

@ -33,22 +33,22 @@ namespace X64
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
constexpr RegisterX64 rState = r15; // lua_State* L
constexpr RegisterX64 rBase = r14; // StkId base
constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
constexpr RegisterX64 rConstants = r12; // TValue* k
inline constexpr RegisterX64 rState = r15; // lua_State* L
inline constexpr RegisterX64 rBase = r14; // StkId base
inline constexpr RegisterX64 rNativeContext = r13; // NativeContext* context
inline constexpr RegisterX64 rConstants = r12; // TValue* k
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 kExtraLocals = 3; // Number of 8 byte slots available for specialized local variables specified below
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");
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
constexpr uint8_t kSystemVUsableXmmRegs = 16; // All xmm regs are volatile
inline 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 kSystemVUsableXmmRegs = 16; // All xmm regs are volatile
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
// 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
constexpr unsigned kStackLocalStorage = 8 * kExtraLocals;
constexpr unsigned kStackSpillStorage = 8 * kSpillSlots;
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 kStackAlign = 8; // Bytes we need to align the stack for non-vol xmm register storage
inline constexpr unsigned kStackLocalStorage = 8 * kExtraLocals;
inline constexpr unsigned kStackSpillStorage = 8 * kSpillSlots;
inline constexpr unsigned kStackExtraArgumentStorage = 2 * 8; // Bytes for 5th and 6th function call arguments used 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)
{
@ -77,19 +77,19 @@ inline unsigned getNonVolXmmStorageSize(ABIX64 abi, uint8_t xmmRegCount)
}
// Useful offsets to specific parts
constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage;
constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage;
inline constexpr unsigned kStackOffsetToLocals = kStackExtraArgumentStorage + kStackRegHomeStorage;
inline constexpr unsigned kStackOffsetToSpillSlots = kStackOffsetToLocals + kStackLocalStorage;
inline unsigned getFullStackSize(ABIX64 abi, uint8_t xmmRegCount)
{
return kStackOffsetToSpillSlots + kStackSpillStorage + getNonVolXmmStorageSize(abi, xmmRegCount) + kStackAlign;
}
constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl
constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackOffsetToLocals + 16];
inline constexpr OperandX64 sClosure = qword[rsp + kStackOffsetToLocals + 0]; // Closure* cl
inline constexpr OperandX64 sCode = qword[rsp + kStackOffsetToLocals + 8]; // Instruction* code
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)
{

View file

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

View file

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

View file

@ -50,15 +50,15 @@ ISOCLINE_SOURCES=extern/isocline/src/isocline.c
ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o)
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_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_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_TARGET=$(BUILD)/luau-analyze

View file

@ -59,7 +59,18 @@ public:
virtual NavigateResult toParent() = 0;
virtual NavigateResult toChild(const std::string& component) = 0;
enum class ConfigBehavior
{
GetAlias,
GetConfig
};
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;
};
@ -94,7 +105,8 @@ private:
NavigationContext& navigationContext;
ErrorHandler& errorHandler;
Luau::Config config;
std::optional<std::string> foundAliasValue;
};
} // namespace Luau::Require

View file

@ -78,7 +78,7 @@ Error Navigator::navigateImpl(std::string_view path)
if (Error error = navigateToAndPopulateConfig(alias))
return error;
if (!config.aliases.contains(alias))
if (!foundAliasValue)
{
if (alias != "self")
return "@" + alias + " is not a valid alias";
@ -93,7 +93,7 @@ Error Navigator::navigateImpl(std::string_view path)
return std::nullopt;
}
if (Error error = navigateToAlias(alias, config.aliases[alias].value))
if (Error error = navigateToAlias(alias, *foundAliasValue))
return error;
if (Error error = navigateThroughPath(path))
return error;
@ -169,12 +169,20 @@ Error Navigator::navigateToAlias(const std::string& alias, const std::string& va
Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
{
while (!config.aliases.contains(desiredAlias))
Luau::Config config;
while (!foundAliasValue)
{
if (navigationContext.toParent() != NavigationContext::NavigateResult::Success)
break;
if (navigationContext.isConfigPresent())
{
if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias)
{
foundAliasValue = navigationContext.getAlias(desiredAlias);
}
else
{
std::optional<std::string> configContents = navigationContext.getConfig();
if (!configContents)
@ -188,6 +196,10 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias)
if (Error error = Luau::parseConfig(*configContents, config, opts))
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).
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.
// 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);
// 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);
}
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
{
return getStringFromCWriter(config->get_config, initalFileBufferSize);
@ -112,17 +124,45 @@ std::optional<std::string> RuntimeNavigationContext::getStringFromCWriter(
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)
: L(L)
, errorPrefix("error requiring module \"" + std::move(requiredPath) + "\": ")
size_t size;
luarequire_WriteResult result = writer(L, ctx, input.c_str(), buffer.data(), buffer.size(), &size);
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)
{
std::string fullError = errorPrefix + std::move(message);
luaL_errorL(L, "%s", fullError.c_str());
errorMessage = errorPrefix + std::move(message);
}
const std::string& RuntimeErrorHandler::getReportedError() const
{
return errorMessage;
}
} // namespace Luau::Require

View file

@ -27,6 +27,8 @@ public:
NavigateResult toChild(const std::string& component) 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;
// 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),
size_t initalBufferSize
) 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;
lua_State* L;
@ -47,15 +54,18 @@ private:
std::string requirerChunkname;
};
// Non-throwing error reporter
class RuntimeErrorHandler : public ErrorHandler
{
public:
RuntimeErrorHandler(lua_State* L, std::string requiredPath);
RuntimeErrorHandler(std::string requiredPath);
void reportError(std::string message) override;
const std::string& getReportedError() const;
private:
lua_State* L;
std::string errorPrefix;
std::string errorMessage;
};
} // 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");
if (!config.is_config_present)
luaL_error(L, "require configuration is missing required function pointer: is_config_present");
if (!config.get_config)
luaL_error(L, "require configuration is missing required function pointer: get_config");
if (config.get_alias && config.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)
luaL_error(L, "require configuration is missing required function pointer: load");
}

View file

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

View file

@ -398,9 +398,11 @@ target_sources(isocline PRIVATE
target_sources(Luau.CLI.lib PRIVATE
CLI/include/Luau/FileUtils.h
CLI/include/Luau/Flags.h
CLI/include/Luau/VfsNavigator.h
CLI/src/FileUtils.cpp
CLI/src/Flags.cpp
CLI/src/VfsNavigator.cpp
)
if(TARGET Luau.Repl.CLI)
@ -409,14 +411,12 @@ if(TARGET Luau.Repl.CLI)
CLI/include/Luau/Coverage.h
CLI/include/Luau/Profiler.h
CLI/include/Luau/ReplRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Coverage.cpp
CLI/src/Profiler.cpp
CLI/src/Repl.cpp
CLI/src/ReplEntry.cpp
CLI/src/ReplRequirer.cpp
CLI/src/RequirerUtils.cpp
)
endif()
@ -424,11 +424,9 @@ if(TARGET Luau.Analyze.CLI)
# Luau.Analyze.CLI Sources
target_sources(Luau.Analyze.CLI PRIVATE
CLI/include/Luau/AnalyzeRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Analyze.cpp
CLI/src/AnalyzeRequirer.cpp
CLI/src/RequirerUtils.cpp
)
endif()
@ -563,13 +561,11 @@ if(TARGET Luau.CLI.Test)
CLI/include/Luau/Coverage.h
CLI/include/Luau/Profiler.h
CLI/include/Luau/ReplRequirer.h
CLI/include/Luau/RequirerUtils.h
CLI/src/Coverage.cpp
CLI/src/Profiler.cpp
CLI/src/Repl.cpp
CLI/src/ReplRequirer.cpp
CLI/src/RequirerUtils.cpp
tests/RegisterCallbacks.h
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_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 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
#define LUA_VECTOR_SIZE 3 // must be 3 or 4
#endif

View file

@ -1479,16 +1479,6 @@ void lua_setuserdatametatable(lua_State* L, int tag)
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)
{
api_check(L, unsigned(tag) < LUA_UTAG_LIMIT);

View file

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

View file

@ -12,11 +12,16 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAGVARIABLE(LuauCurrentLineBounds)
static const char* getfuncname(Closure* f);
static int currentpc(lua_State* L, CallInfo* ci)
{
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)

View file

@ -4,7 +4,9 @@
#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_forerror(L, o, what) luaG_forerrorL(L, o, what)

View file

@ -14,6 +14,8 @@
#include <string.h>
#include <stdio.h>
LUAU_FASTFLAG(LuauCurrentLineBounds)
static void validateobjref(global_State* g, GCObject* f, GCObject* t)
{
LUAU_ASSERT(!isdead(g, t));
@ -462,7 +464,7 @@ static void dumpthread(FILE* f, lua_State* th)
else if (isLua(ci))
{
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);
if (var && var->varname)

View file

@ -96,7 +96,7 @@
#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
* the sizes of all critical structures down.
*/
@ -120,10 +120,12 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table
#endif
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(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;
// 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 busyBlocks; // number of blocks allocated out of this page
union
{
// provide additional padding based on current object size to provide 16 byte alignment of data
// later static_assert checks that this requirement is held
char padding[sizeof(void*) == 8 ? 8 : 12];
char data[1];
double align1;
void* align2;
};
};
static_assert(offsetof(lua_Page, data) % 16 == 0, "data must be 16 byte aligned to provide properly aligned allocation of userdata objects");
l_noret luaM_toobig(lua_State* L)
{
luaG_runerror(L, "memory allocation error: block too big");

View file

@ -265,11 +265,9 @@ typedef struct Udata
struct LuaTable* metatable;
union
{
char data[1]; // userdata is allocated right after the header
L_Umaxalign dummy; // ensures maximum alignment for data
};
// 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
alignas(8) char data[1];
} Udata;
typedef struct LuauBuffer
@ -278,11 +276,7 @@ typedef struct LuauBuffer
unsigned int len;
union
{
char data[1]; // buffer is allocated right after the header
L_Umaxalign dummy; // ensures maximum alignment for data
};
alignas(8) char data[1];
} Buffer;
/*

View file

@ -366,7 +366,7 @@ static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key)
int k;
double n = nvalue(key);
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];
}
@ -604,7 +604,7 @@ static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key)
const TValue* luaH_getnum(LuaTable* t, int key)
{
// (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];
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)
{
// (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];
// hash fallback
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
if (cast_to(unsigned int, f - 1) < cast_to(unsigned int, src->sizearray) &&
cast_to(unsigned int, t - 1) < cast_to(unsigned int, 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))
if (unsigned(f) - 1 < unsigned(src->sizearray) && unsigned(t) - 1 < unsigned(dst->sizearray) &&
unsigned(f) - 1 + unsigned(n) <= unsigned(src->sizearray) && unsigned(t) - 1 + unsigned(n) <= unsigned(dst->sizearray))
{
TValue* srcarray = src->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)
#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 void luaU_freeudata(lua_State* L, Udata* u, struct lua_Page* page);

View file

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

View file

@ -13,7 +13,8 @@
#include <string.h>
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
LUAU_FASTFLAGVARIABLE(LuauLoadNoOomThrow)
template<typename T>
struct TempBuffer
{
@ -21,11 +22,20 @@ struct TempBuffer
T* data;
size_t count;
TempBuffer()
: L(NULL)
, data(NULL)
, count(0)
{
LUAU_ASSERT(FFlag::LuauLoadNoOomThrow);
}
TempBuffer(lua_State* L, size_t count)
: L(L)
, data(luaM_newarray(L, count, T, 0))
, count(count)
{
LUAU_ASSERT(!FFlag::LuauLoadNoOomThrow);
}
TempBuffer(const TempBuffer&) = delete;
@ -36,9 +46,18 @@ struct TempBuffer
~TempBuffer() noexcept
{
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)
{
LUAU_ASSERT(index < count);
@ -242,7 +261,360 @@ static void remapUserdataTypes(char* data, size_t size, uint8_t* userdataRemappi
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;
@ -592,3 +964,54 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
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(
OUTPUT ${LUAU_PB_SOURCES}
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}
DEPENDS protobuf::protoc ${CMAKE_CURRENT_SOURCE_DIR}/luau.proto
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
)
add_executable(Luau.Fuzz.Proto)

View file

@ -11,7 +11,6 @@
using namespace Luau;
LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
struct JsonEncoderFixture
@ -460,9 +459,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr")
AstStat* expr = expectParseStatement("@checked function a(b) return c end");
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,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"}})";
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"}})";
CHECK(toJson(expr) == expected);
}

View file

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

View file

@ -23,6 +23,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileFixTypeFunctionSkip)
using namespace Luau;
@ -2971,6 +2972,33 @@ TEST_CASE("TypeFunction")
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")
{
Luau::BytecodeBuilder bcb;

View file

@ -31,10 +31,15 @@ extern int optimizationLevel;
void luaC_fullgc(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_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC)
LUAU_FASTFLAG(LuauYieldableContinuations)
LUAU_FASTFLAG(LuauCurrentLineBounds)
LUAU_FASTFLAG(LuauLoadNoOomThrow)
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
}
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")
{
lua_CompileOptions copts = defaultOptions();
@ -2495,6 +2613,46 @@ TEST_CASE("UserdataApi")
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")
{
StateRef globalState(luaL_newstate(), lua_close);
@ -3016,6 +3174,8 @@ TEST_CASE("HugeFunction")
TEST_CASE("HugeFunctionLoadFailure")
{
ScopedFastFlag luauLoadNoOomThrow{FFlag::LuauLoadNoOomThrow, true};
// 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
// that would cause issues during garbage collection.
@ -3066,15 +3226,11 @@ TEST_CASE("HugeFunctionLoadFailure")
luaL_sandbox(L);
luaL_sandboxthread(L);
try
{
luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
REQUIRE(false); // The luau_load should fail with an exception
}
catch (const std::exception& ex)
{
REQUIRE(strcmp(ex.what(), "lua_exception: not enough memory") == 0);
}
int status = luau_load(L, "=HugeFunction", bytecode, bytecodeSize, 0);
REQUIRE(status == 1);
const char* error = lua_tostring(L, -1);
CHECK(strcmp(error, "not enough memory") == 0);
luaC_fullgc(L);
}

View file

@ -14,6 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
struct DataFlowGraphFixture
{
@ -46,6 +47,17 @@ struct DataFlowGraphFixture
REQUIRE(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");
@ -119,6 +131,8 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "phi")
TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while")
{
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"(
local x
@ -133,8 +147,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_while")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1);
CHECK(x1 == x2);
auto phi = get<Phi>(x2);
REQUIRE(phi);
checkOperands(phi, {x0, x1});
}
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")
{
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"(
local x
@ -171,7 +188,7 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_repeat")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1);
CHECK(x0 != x1);
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")
{
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"(
local x
@ -209,8 +228,9 @@ TEST_CASE_FIXTURE(DataFlowGraphFixture, "mutate_local_not_owned_by_for")
DefId x1 = getDef<AstExprLocal, 1>(); // x = true
DefId x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1);
CHECK(x1 == x2);
auto phi = get<Phi>(x2);
REQUIRE(phi);
checkOperands(phi, {x0, x1});
}
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")
{
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"(
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 x2 = getDef<AstExprLocal, 2>(); // local y = x
CHECK(x0 == x1);
CHECK(x1 == x2);
auto phi = get<Phi>(x2);
REQUIRE(phi);
checkOperands(phi, {x0, x1});
}
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")
{
ScopedFastFlag _{FFlag::LuauDfgAllowUpdatesInLoops, true};
dfg(R"(
local t = {}
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 x3 = getDef<AstExprIndexName, 3>(); // local y = t.x
CHECK(x1 == x2);
CHECK(x2 == x3);
auto phi = get<Phi>(x3);
REQUIRE(phi);
checkOperands(phi, {x1, x2});
}
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(LuauCloneTypeAliasBindings)
LUAU_FASTFLAG(LuauDoNotClonePersistentBindings)
LUAU_FASTFLAG(LuauUserTypeFunTypecheck)
LUAU_FASTFLAG(LuauBetterScopeSelection)
LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
@ -3065,8 +3064,6 @@ z = a.P.E
TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local")
{
ScopedFastFlag luauUserTypeFunTypecheck{FFlag::LuauUserTypeFunTypecheck, true};
const std::string source = R"(--!strict
type function foo(x: type): type
if x.tag == "singleton" then

View file

@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
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")
{
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
Frontend fe{&fileResolver, &configResolver, {false}};
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>
if (FFlag::LuauSolverV2)
{
REQUIRE_EQ(
"Type\n\t"
"'{ count: string }'"
"\ncould not be converted into\n\t"
"'{ Count: number }'",
toString(result.errors[0])
);
CHECK_EQ(
"Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'",
toString(result.errors[0]));
}
else
REQUIRE_EQ(

View file

@ -15,7 +15,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization2)
LUAU_FASTFLAG(LuauNonReentrantGeneralization3)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
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})")
{
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true};
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
TableType tt;
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})) -> ()")
{
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization2, true};
ScopedFastFlag sff{FFlag::LuauNonReentrantGeneralization3, true};
auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType();

View file

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

View file

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

View file

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

View file

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

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