Sync to upstream/release/659

This commit is contained in:
Menarul Alam 2025-01-31 17:33:47 -08:00
parent 23241e2afc
commit 5f0bd2fcdd
45 changed files with 860 additions and 890 deletions

View file

@ -149,7 +149,7 @@ using EType = EqSat::Language<
struct StringCache
{
Allocator allocator;
DenseHashMap<size_t, StringId> strings{{}};
DenseHashMap<std::string_view, StringId> strings{{}};
std::vector<std::string_view> views;
StringId add(std::string_view s);

View file

@ -63,7 +63,7 @@ void check(
NotNull<BuiltinTypes> builtinTypes,
NotNull<Simplifier> simplifier,
NotNull<TypeFunctionRuntime> typeFunctionRuntime,
NotNull<UnifierSharedState> sharedState,
NotNull<UnifierSharedState> unifierState,
NotNull<TypeCheckLimits> limits,
DcrLogger* logger,
const SourceModule& sourceModule,
@ -116,14 +116,14 @@ private:
std::optional<StackPusher> pushStack(AstNode* node);
void checkForInternalTypeFunction(TypeId ty, Location location);
TypeId checkForTypeFunctionInhabitance(TypeId instance, Location location);
TypePackId lookupPack(AstExpr* expr);
TypePackId lookupPack(AstExpr* expr) const;
TypeId lookupType(AstExpr* expr);
TypeId lookupAnnotation(AstType* annotation);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation);
TypeId lookupExpectedType(AstExpr* expr);
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena);
std::optional<TypePackId> lookupPackAnnotation(AstTypePack* annotation) const;
TypeId lookupExpectedType(AstExpr* expr) const;
TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena) const;
TypePackId reconstructPack(AstArray<AstExpr*> exprs, TypeArena& arena);
Scope* findInnermostScope(Location location);
Scope* findInnermostScope(Location location) const;
void visit(AstStat* stat);
void visit(AstStatIf* ifStatement);
void visit(AstStatWhile* whileStatement);
@ -160,7 +160,7 @@ private:
void visit(AstExprVarargs* expr);
void visitCall(AstExprCall* call);
void visit(AstExprCall* call);
std::optional<TypeId> tryStripUnionFromNil(TypeId ty);
std::optional<TypeId> tryStripUnionFromNil(TypeId ty) const;
TypeId stripFromNilAndReport(TypeId ty, const Location& location);
void visitExprName(AstExpr* expr, Location location, const std::string& propName, ValueContext context, TypeId astIndexExprTy);
void visit(AstExprIndexName* indexName, ValueContext context);

View file

@ -40,7 +40,7 @@ struct InConditionalContext
TypeContext* typeContext;
TypeContext oldValue;
InConditionalContext(TypeContext* c)
explicit InConditionalContext(TypeContext* c)
: typeContext(c)
, oldValue(*c)
{

View file

@ -33,8 +33,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2)
LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix)
LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression)
LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2)
LUAU_FASTFLAG(LuauVectorDefinitionsExtra)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType2)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
namespace Luau
@ -310,28 +309,25 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
addGlobalBinding(globals, "string", it->second.type(), "@luau");
// Setup 'vector' metatable
if (FFlag::LuauVectorDefinitionsExtra)
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end())
{
TypeId vectorTy = it->second.type;
ClassType* vectorCls = getMutable<ClassType>(vectorTy);
TypeId vectorTy = it->second.type;
ClassType* vectorCls = getMutable<ClassType>(vectorTy);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed});
TableType* metatableTy = Luau::getMutable<TableType>(vectorCls->metatable);
metatableTy->props["__add"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__sub"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__unm"] = {makeFunction(arena, vectorTy, {}, {vectorTy})};
metatableTy->props["__add"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__sub"] = {makeFunction(arena, vectorTy, {vectorTy}, {vectorTy})};
metatableTy->props["__unm"] = {makeFunction(arena, vectorTy, {}, {vectorTy})};
std::initializer_list<TypeId> mulOverloads{
makeFunction(arena, vectorTy, {vectorTy}, {vectorTy}),
makeFunction(arena, vectorTy, {builtinTypes->numberType}, {vectorTy}),
};
metatableTy->props["__mul"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__div"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__idiv"] = {makeIntersection(arena, mulOverloads)};
}
std::initializer_list<TypeId> mulOverloads{
makeFunction(arena, vectorTy, {vectorTy}, {vectorTy}),
makeFunction(arena, vectorTy, {builtinTypes->numberType}, {vectorTy}),
};
metatableTy->props["__mul"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__div"] = {makeIntersection(arena, mulOverloads)};
metatableTy->props["__idiv"] = {makeIntersection(arena, mulOverloads)};
}
// next<K, V>(t: Table<K, V>, i: K?) -> (K?, V)
@ -453,7 +449,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
ttv->props["foreachi"].deprecated = true;
attachMagicFunction(ttv->props["pack"].type(), std::make_shared<MagicPack>());
if (FFlag::LuauTableCloneClonesType)
if (FFlag::LuauTableCloneClonesType2)
attachMagicFunction(ttv->props["clone"].type(), std::make_shared<MagicClone>());
if (FFlag::LuauTypestateBuiltins2)
attachMagicFunction(ttv->props["freeze"].type(), std::make_shared<MagicFreeze>());
@ -1405,7 +1401,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
WithPredicate<TypePackId> withPredicate
)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType);
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
auto [paramPack, _predicates] = withPredicate;
@ -1429,7 +1425,7 @@ std::optional<WithPredicate<TypePackId>> MagicClone::handleOldSolver(
bool MagicClone::infer(const MagicFunctionCallContext& context)
{
LUAU_ASSERT(FFlag::LuauTableCloneClonesType);
LUAU_ASSERT(FFlag::LuauTableCloneClonesType2);
TypeArena* arena = context.solver->arena;

View file

@ -32,13 +32,9 @@ LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauTypestateBuiltins2)
LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs)
LUAU_FASTFLAGVARIABLE(LuauNewSolverVisitErrorExprLvalues)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPrePopulateClasses)
LUAU_FASTFLAGVARIABLE(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(InferGlobalTypes)
@ -743,12 +739,6 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
continue;
}
if (!FFlag::LuauUserTypeFunExportedAndLocal && scope->parent != globalScope)
{
reportError(function->location, GenericError{"Local user-defined functions are not supported yet"});
continue;
}
ScopePtr defnScope = childScope(function, scope);
// Create TypeFunctionInstanceType
@ -774,11 +764,8 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
UserDefinedFunctionData udtfData;
if (FFlag::LuauUserTypeFunExportedAndLocal)
{
udtfData.owner = module;
udtfData.definition = function;
}
udtfData.owner = module;
udtfData.definition = function;
TypeId typeFunctionTy = arena->addType(
TypeFunctionInstanceType{NotNull{&builtinTypeFunctions().userFunc}, std::move(typeParams), {}, function->name, udtfData}
@ -787,7 +774,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy};
// Set type bindings and definition locations for this user-defined type function
if (FFlag::LuauUserTypeFunExportedAndLocal && function->exported)
if (function->exported)
scope->exportedTypeBindings[function->name.value] = std::move(typeFunction);
else
scope->privateTypeBindings[function->name.value] = std::move(typeFunction);
@ -822,77 +809,74 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
}
}
if (FFlag::LuauUserTypeFunExportedAndLocal)
// Additional pass for user-defined type functions to fill in their environments completely
for (AstStat* stat : block->body)
{
// 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 (auto function = stat->as<AstStatTypeFunction>())
// Find the type function we have already created
TypeFunctionInstanceType* mainTypeFun = nullptr;
if (auto it = scope->privateTypeBindings.find(function->name.value); it != scope->privateTypeBindings.end())
mainTypeFun = getMutable<TypeFunctionInstanceType>(it->second.type);
if (!mainTypeFun)
{
// Find the type function we have already created
TypeFunctionInstanceType* mainTypeFun = nullptr;
if (auto it = scope->privateTypeBindings.find(function->name.value); it != scope->privateTypeBindings.end())
if (auto it = scope->exportedTypeBindings.find(function->name.value); it != scope->exportedTypeBindings.end())
mainTypeFun = getMutable<TypeFunctionInstanceType>(it->second.type);
}
if (!mainTypeFun)
// Fill it with all visible type functions
if (mainTypeFun)
{
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
size_t level = 0;
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
if (auto it = scope->exportedTypeBindings.find(function->name.value); it != scope->exportedTypeBindings.end())
mainTypeFun = getMutable<TypeFunctionInstanceType>(it->second.type);
}
// Fill it with all visible type functions
if (FFlag::LuauUserTypeFunUpdateAllEnvs && mainTypeFun)
{
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
size_t level = 0;
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
for (auto& [name, tf] : curr->privateTypeBindings)
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment.find(name))
continue;
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++;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level);
}
}
else if (mainTypeFun)
{
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
for (auto& [name, tf] : curr->exportedTypeBindings)
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment_DEPRECATED.find(name))
continue;
if (userFuncData.environment.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
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_DEPRECATED.find(name))
continue;
level++;
}
}
else if (mainTypeFun)
{
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
for (Scope* curr = scope.get(); curr; curr = curr->parent.get())
{
for (auto& [name, tf] : curr->privateTypeBindings)
{
if (userFuncData.environment_DEPRECATED.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
for (auto& [name, tf] : curr->exportedTypeBindings)
{
if (userFuncData.environment_DEPRECATED.find(name))
continue;
if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition;
}
}
}
@ -1622,24 +1606,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunction* function)
{
if (!FFlag::LuauUserTypeFunNoExtraConstraint)
{
// If a type function with the same name was already defined, we skip over
auto bindingIt = scope->privateTypeBindings.find(function->name.value);
if (bindingIt == scope->privateTypeBindings.end())
return ControlFlow::None;
TypeFun typeFunction = bindingIt->second;
// Adding typeAliasExpansionConstraint on user-defined type function for the constraint solver
if (auto typeFunctionTy = get<TypeFunctionInstanceType>(follow(typeFunction.type)))
{
TypeId expansionTy =
arena->addType(PendingExpansionType{{}, function->name, typeFunctionTy->typeArguments, typeFunctionTy->packArguments});
addConstraint(scope, function->location, TypeAliasExpansionConstraint{/* target */ expansionTy});
}
}
return ControlFlow::None;
}
@ -2785,15 +2751,12 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExpr* expr, Type
visitLValue(scope, e, rhsType);
else if (auto e = expr->as<AstExprError>())
{
if (FFlag::LuauNewSolverVisitErrorExprLvalues)
// If we end up with some sort of error expression in an lvalue
// position, at least go and check the expressions so that when
// we visit them later, there aren't any invalid assumptions.
for (auto subExpr : e->expressions)
{
// If we end up with some sort of error expression in an lvalue
// position, at least go and check the expressions so that when
// we visit them later, there aren't any invalid assumptions.
for (auto subExpr : e->expressions)
{
check(scope, subExpr);
}
check(scope, subExpr);
}
}
else
@ -3255,8 +3218,7 @@ TypeId ConstraintGenerator::resolveReferenceType(
if (alias.has_value())
{
// If the alias is not generic, we don't need to set up a blocked type and an instantiation constraint
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty() &&
(!FFlag::LuauUserTypeFunNoExtraConstraint || !ref->hasParameterList))
if (alias.has_value() && alias->typeParams.empty() && alias->typePackParams.empty() && !ref->hasParameterList)
{
result = alias->type;
}

View file

@ -35,9 +35,9 @@ LUAU_FASTFLAGVARIABLE(LuauRemoveNotAnyHack)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations)
LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes)
LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope)
namespace Luau
{
@ -823,6 +823,9 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
TypeId tableTy =
arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, tableTy);
unify(constraint, nextTy, tableTy);
auto it = begin(c.variables);
@ -959,16 +962,6 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
if (auto typeFn = get<TypeFunctionInstanceType>(follow(tf->type)))
pushConstraint(NotNull(constraint->scope.get()), constraint->location, ReduceConstraint{tf->type});
if (!FFlag::LuauUserTypeFunNoExtraConstraint)
{
// If there are no parameters to the type function we can just use the type directly
if (tf->typeParams.empty() && tf->typePackParams.empty())
{
bindResult(tf->type);
return true;
}
}
// Due to how pending expansion types and TypeFun's are created
// If this check passes, we have created a cyclic / corecursive type alias
// of size 0
@ -981,14 +974,11 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true;
}
if (FFlag::LuauUserTypeFunNoExtraConstraint)
// If there are no parameters to the type function we can just use the type directly
if (tf->typeParams.empty() && tf->typePackParams.empty())
{
// If there are no parameters to the type function we can just use the type directly
if (tf->typeParams.empty() && tf->typePackParams.empty())
{
bindResult(tf->type);
return true;
}
bindResult(tf->type);
return true;
}
auto [typeArguments, packArguments] = saturateArguments(arena, builtinTypes, *tf, petv->typeArguments, petv->packArguments);
@ -1854,6 +1844,10 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
else
{
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* upperTable = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(upperTable);
@ -2637,6 +2631,10 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
NotNull<Scope> scope{ft->scope};
const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope});
if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope)
trackInteriorFreeType(constraint->scope, newUpperBound);
TableType* tt = getMutable<TableType>(newUpperBound);
LUAU_ASSERT(tt);
TypeId propType = freshType(arena, builtinTypes, scope);

View file

@ -1,7 +1,6 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "Luau/BuiltinDefinitions.h"
LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra)
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition)
LUAU_FASTFLAG(LuauVector2Constructor)
@ -488,58 +487,6 @@ declare buffer: {
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc_NoExtra_NoVector2Ctor_DEPRECATED = R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type
declare class vector end
declare vector: {
create: @checked (x: number, y: number, z: number) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc_NoExtra_DEPRECATED = R"BUILTIN_SRC(
-- TODO: this will be replaced with a built-in primitive type
declare class vector end
declare vector: {
create: @checked (x: number, y: number, z: number?) -> vector,
magnitude: @checked (vec: vector) -> number,
normalize: @checked (vec: vector) -> vector,
cross: @checked (vec1: vector, vec2: vector) -> vector,
dot: @checked (vec1: vector, vec2: vector) -> number,
angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number,
floor: @checked (vec: vector) -> vector,
ceil: @checked (vec: vector) -> vector,
abs: @checked (vec: vector) -> vector,
sign: @checked (vec: vector) -> vector,
clamp: @checked (vec: vector, min: vector, max: vector) -> vector,
max: @checked (vector, ...vector) -> vector,
min: @checked (vector, ...vector) -> vector,
zero: vector,
one: vector,
}
)BUILTIN_SRC";
static const std::string kBuiltinDefinitionVectorSrc_NoVector2Ctor_DEPRECATED = R"BUILTIN_SRC(
-- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties
@ -617,20 +564,10 @@ std::string getBuiltinDefinitionSource()
result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED;
if (FFlag::LuauVectorDefinitionsExtra)
{
if (FFlag::LuauVector2Constructor)
result += kBuiltinDefinitionVectorSrc;
else
result += kBuiltinDefinitionVectorSrc_NoVector2Ctor_DEPRECATED;
}
if (FFlag::LuauVector2Constructor)
result += kBuiltinDefinitionVectorSrc;
else
{
if (FFlag::LuauVector2Constructor)
result += kBuiltinDefinitionVectorSrc_NoExtra_DEPRECATED;
else
result += kBuiltinDefinitionVectorSrc_NoExtra_NoVector2Ctor_DEPRECATED;
}
result += kBuiltinDefinitionVectorSrc_NoVector2Ctor_DEPRECATED;
return result;
}

View file

@ -92,18 +92,24 @@ size_t TTable::Hash::operator()(const TTable& value) const
return hash;
}
uint32_t StringCache::add(std::string_view s)
StringId StringCache::add(std::string_view s)
{
size_t hash = std::hash<std::string_view>()(s);
if (uint32_t* it = strings.find(hash))
/* Important subtlety: This use of DenseHashMap<std::string_view, StringId>
* is okay because std::hash<std::string_view> works solely on the bytes
* referred by the string_view.
*
* In other words, two string views which contain the same bytes will have
* the same hash whether or not their addresses are the same.
*/
if (StringId* it = strings.find(s))
return *it;
char* storage = static_cast<char*>(allocator.allocate(s.size()));
memcpy(storage, s.data(), s.size());
uint32_t result = uint32_t(views.size());
StringId result = StringId(views.size());
views.emplace_back(storage, s.size());
strings[hash] = result;
strings[s] = result;
return result;
}
@ -390,6 +396,16 @@ Id toId(
{
LUAU_ASSERT(tfun->packArguments.empty());
if (tfun->userFuncName) {
// TODO: User defined type functions are pseudo-effectful: error
// reporting is done via the `print` statement, so running a
// UDTF multiple times may end up double erroring. egraphs
// currently may induce type functions to be reduced multiple
// times. We should probably opt _not_ to process user defined
// type functions at all.
return egraph.add(TOpaque{ty});
}
std::vector<Id> parts;
parts.reserve(tfun->typeArguments.size());
for (TypeId part : tfun->typeArguments)

View file

@ -39,7 +39,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAGVARIABLE(LuauStoreCommentsForDefinitionFiles)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
@ -52,6 +51,7 @@ LUAU_FASTFLAG(StudioReportLuauAny2)
LUAU_FASTFLAGVARIABLE(LuauStoreSolverTypeOnModule)
LUAU_FASTFLAGVARIABLE(LuauReferenceAllocatorInNewSolver)
LUAU_FASTFLAGVARIABLE(LuauSelectivelyRetainDFGArena)
namespace Luau
{
@ -138,7 +138,7 @@ static ParseResult parseSourceForModule(std::string_view source, Luau::SourceMod
sourceModule.root = parseResult.root;
sourceModule.mode = Mode::Definition;
if (FFlag::LuauStoreCommentsForDefinitionFiles && options.captureComments)
if (options.captureComments)
{
sourceModule.hotcomments = parseResult.hotcomments;
sourceModule.commentLocations = parseResult.commentLocations;
@ -1049,6 +1049,11 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
freeze(module->interfaceTypes);
module->internalTypes.clear();
if (FFlag::LuauSelectivelyRetainDFGArena)
{
module->defArena.allocator.clear();
module->keyArena.allocator.clear();
}
module->astTypes.clear();
module->astTypePacks.clear();

View file

@ -17,12 +17,11 @@
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant)
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200)
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance);
LUAU_FASTFLAGVARIABLE(LuauIntersectNormalsNeedsToTrackResourceLimits);
LUAU_FASTFLAGVARIABLE(LuauNormalizationTracksCyclicPairsThroughInhabitance)
namespace Luau
{
@ -3043,12 +3042,9 @@ NormalizationResult Normalizer::intersectTyvarsWithTy(
// See above for an explaination of `ignoreSmallerTyvars`.
NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{
if (FFlag::LuauIntersectNormalsNeedsToTrackResourceLimits)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
}
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return NormalizationResult::HitLimits;
if (!get<NeverType>(there.tops))
{

View file

@ -9,6 +9,8 @@
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
LUAU_FASTFLAGVARIABLE(LuauDontInPlaceMutateTableType)
namespace Luau
{
@ -236,6 +238,8 @@ TypeId matchLiteralType(
return exprType;
}
DenseHashSet<AstExprConstantString*> keysToDelete{nullptr};
for (const AstExprTable::Item& item : exprTable->items)
{
if (isRecord(item))
@ -280,7 +284,10 @@ TypeId matchLiteralType(
else
tableTy->indexer = TableIndexer{expectedTableTy->indexer->indexType, matchedType};
tableTy->props.erase(keyStr);
if (FFlag::LuauDontInPlaceMutateTableType)
keysToDelete.insert(item.key->as<AstExprConstantString>());
else
tableTy->props.erase(keyStr);
}
// If it's just an extra property and the expected type
@ -387,6 +394,16 @@ TypeId matchLiteralType(
LUAU_ASSERT(!"Unexpected");
}
if (FFlag::LuauDontInPlaceMutateTableType)
{
for (const auto& key: keysToDelete)
{
const AstArray<char>& s = key->value;
std::string keyStr{s.data, s.data + s.size};
tableTy->props.erase(keyStr);
}
}
// Keys that the expectedType says we should have, but that aren't
// specified by the AST fragment.
//

View file

@ -7,7 +7,6 @@
#include "Luau/DcrLogger.h"
#include "Luau/DenseHash.h"
#include "Luau/Error.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Instantiation.h"
#include "Luau/Metamethods.h"
#include "Luau/Normalize.h"
@ -27,8 +26,6 @@
#include "Luau/VisitType.h"
#include <algorithm>
#include <iostream>
#include <ostream>
LUAU_FASTFLAG(DebugLuauMagicTypes)
@ -176,7 +173,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor
DenseHashSet<TypeId> mentionedFunctions{nullptr};
DenseHashSet<TypePackId> mentionedFunctionPacks{nullptr};
InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
explicit InternalTypeFunctionFinder(std::vector<TypeId>& declStack)
{
TypeFunctionFinder f;
for (TypeId fn : declStack)
@ -507,7 +504,7 @@ TypeId TypeChecker2::checkForTypeFunctionInhabitance(TypeId instance, Location l
return instance;
}
TypePackId TypeChecker2::lookupPack(AstExpr* expr)
TypePackId TypeChecker2::lookupPack(AstExpr* expr) const
{
// If a type isn't in the type graph, it probably means that a recursion limit was exceeded.
// We'll just return anyType in these cases. Typechecking against any is very fast and this
@ -557,7 +554,7 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation)
return checkForTypeFunctionInhabitance(follow(*ty), annotation->location);
}
std::optional<TypePackId> TypeChecker2::lookupPackAnnotation(AstTypePack* annotation)
std::optional<TypePackId> TypeChecker2::lookupPackAnnotation(AstTypePack* annotation) const
{
TypePackId* tp = module->astResolvedTypePacks.find(annotation);
if (tp != nullptr)
@ -565,7 +562,7 @@ std::optional<TypePackId> TypeChecker2::lookupPackAnnotation(AstTypePack* annota
return {};
}
TypeId TypeChecker2::lookupExpectedType(AstExpr* expr)
TypeId TypeChecker2::lookupExpectedType(AstExpr* expr) const
{
if (TypeId* ty = module->astExpectedTypes.find(expr))
return follow(*ty);
@ -573,7 +570,7 @@ TypeId TypeChecker2::lookupExpectedType(AstExpr* expr)
return builtinTypes->anyType;
}
TypePackId TypeChecker2::lookupExpectedPack(AstExpr* expr, TypeArena& arena)
TypePackId TypeChecker2::lookupExpectedPack(AstExpr* expr, TypeArena& arena) const
{
if (TypeId* ty = module->astExpectedTypes.find(expr))
return arena.addTypePack(TypePack{{follow(*ty)}, std::nullopt});
@ -597,7 +594,7 @@ TypePackId TypeChecker2::reconstructPack(AstArray<AstExpr*> exprs, TypeArena& ar
return arena.addTypePack(TypePack{head, tail});
}
Scope* TypeChecker2::findInnermostScope(Location location)
Scope* TypeChecker2::findInnermostScope(Location location) const
{
Scope* bestScope = module->getModuleScope().get();
@ -1564,7 +1561,7 @@ void TypeChecker2::visit(AstExprCall* call)
visitCall(call);
}
std::optional<TypeId> TypeChecker2::tryStripUnionFromNil(TypeId ty)
std::optional<TypeId> TypeChecker2::tryStripUnionFromNil(TypeId ty) const
{
if (const UnionType* utv = get<UnionType>(ty))
{

View file

@ -47,10 +47,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauUserTypeFunPrintToError)
LUAU_FASTFLAG(LuauRemoveNotAnyHack)
LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunUpdateAllEnvs)
namespace Luau
{
@ -221,11 +218,8 @@ struct TypeFunctionReducer
template<typename T>
void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult<T> reduction)
{
if (FFlag::LuauUserTypeFunPrintToError)
{
for (auto& message : reduction.messages)
result.messages.emplace_back(location, UserDefinedTypeFunctionError{std::move(message)});
}
for (auto& message : reduction.messages)
result.messages.emplace_back(location, UserDefinedTypeFunctionError{std::move(message)});
if (reduction.result)
replace(subject, *reduction.result);
@ -617,27 +611,16 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
{
auto typeFunction = getMutable<TypeFunctionInstanceType>(instance);
if (FFlag::LuauUserTypeFunExportedAndLocal)
if (typeFunction->userFuncData.owner.expired())
{
if (typeFunction->userFuncData.owner.expired())
{
ctx->ice->ice("user-defined type function module has expired");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
if (!typeFunction->userFuncName || !typeFunction->userFuncData.definition)
{
ctx->ice->ice("all user-defined type functions must have an associated function definition");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
ctx->ice->ice("user-defined type function module has expired");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
else
if (!typeFunction->userFuncName || !typeFunction->userFuncData.definition)
{
if (!ctx->userFuncName)
{
ctx->ice->ice("all user-defined type functions must have an associated function definition");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
ctx->ice->ice("all user-defined type functions must have an associated function definition");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
// If type functions cannot be evaluated because of errors in the code, we do not generate any additional ones
@ -653,36 +636,19 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
return {std::nullopt, Reduction::MaybeOk, {ty}, {}};
}
if (FFlag::LuauUserTypeFunExportedAndLocal && FFlag::LuauUserTypeFunUpdateAllEnvs)
// Ensure that whole type function environment is registered
for (auto& [name, definition] : typeFunction->userFuncData.environment)
{
// Ensure that whole type function environment is registered
for (auto& [name, definition] : typeFunction->userFuncData.environment)
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
{
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition.first))
{
// Failure to register at this point means that original definition had to error out and should not have been present in the
// environment
ctx->ice->ice("user-defined type function reference cannot be registered");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
}
}
else if (FFlag::LuauUserTypeFunExportedAndLocal)
{
// Ensure that whole type function environment is registered
for (auto& [name, definition] : typeFunction->userFuncData.environment_DEPRECATED)
{
if (std::optional<std::string> error = ctx->typeFunctionRuntime->registerFunction(definition))
{
// Failure to register at this point means that original definition had to error out and should not have been present in the
// environment
ctx->ice->ice("user-defined type function reference cannot be registered");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
// Failure to register at this point means that original definition had to error out and should not have been present in the
// environment
ctx->ice->ice("user-defined type function reference cannot be registered");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
}
AstName name = FFlag::LuauUserTypeFunExportedAndLocal ? typeFunction->userFuncData.definition->name : *ctx->userFuncName;
AstName name = typeFunction->userFuncData.definition->name;
lua_State* global = ctx->typeFunctionRuntime->state.get();
@ -693,62 +659,15 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
lua_State* L = lua_newthread(global);
LuauTempThreadPopper popper(global);
if (FFlag::LuauUserTypeFunExportedAndLocal && FFlag::LuauUserTypeFunUpdateAllEnvs)
// Build up the environment table of each function we have visible
for (auto& [_, curr] : typeFunction->userFuncData.environment)
{
// Build up the environment table of each function we have visible
for (auto& [_, curr] : typeFunction->userFuncData.environment)
{
// Environment table has to be filled only once in the current execution context
if (ctx->typeFunctionRuntime->initialized.find(curr.first))
continue;
ctx->typeFunctionRuntime->initialized.insert(curr.first);
// Environment table has to be filled only once in the current execution context
if (ctx->typeFunctionRuntime->initialized.find(curr.first))
continue;
ctx->typeFunctionRuntime->initialized.insert(curr.first);
lua_pushlightuserdata(L, curr.first);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
{
ctx->ice->ice("user-defined type function reference cannot be found in the registry");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
// Build up the environment of the current function, where some might not be visible
lua_getfenv(L, -1);
lua_setreadonly(L, -1, false);
for (auto& [name, definition] : typeFunction->userFuncData.environment)
{
// Filter visibility based on original scope depth
if (definition.second >= curr.second)
{
lua_pushlightuserdata(L, definition.first);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
break; // Don't have to report an error here, we will visit each function in outer loop
lua_setfield(L, -2, name.c_str());
}
}
lua_setreadonly(L, -1, true);
lua_pop(L, 2);
}
// Fetch the function we want to evaluate
lua_pushlightuserdata(L, typeFunction->userFuncData.definition);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
{
ctx->ice->ice("user-defined type function reference cannot be found in the registry");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
}
else if (FFlag::LuauUserTypeFunExportedAndLocal)
{
// Fetch the function we want to evaluate
lua_pushlightuserdata(L, typeFunction->userFuncData.definition);
lua_pushlightuserdata(L, curr.first);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
@ -757,31 +676,37 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
// Build up the environment
// Build up the environment of the current function, where some might not be visible
lua_getfenv(L, -1);
lua_setreadonly(L, -1, false);
for (auto& [name, definition] : typeFunction->userFuncData.environment_DEPRECATED)
for (auto& [name, definition] : typeFunction->userFuncData.environment)
{
lua_pushlightuserdata(L, definition);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
// Filter visibility based on original scope depth
if (definition.second >= curr.second)
{
ctx->ice->ice("user-defined type function reference cannot be found in the registry");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
lua_pushlightuserdata(L, definition.first);
lua_gettable(L, LUA_REGISTRYINDEX);
lua_setfield(L, -2, name.c_str());
if (!lua_isfunction(L, -1))
break; // Don't have to report an error here, we will visit each function in outer loop
lua_setfield(L, -2, name.c_str());
}
}
lua_setreadonly(L, -1, true);
lua_pop(L, 1);
lua_pop(L, 2);
}
else
// Fetch the function we want to evaluate
lua_pushlightuserdata(L, typeFunction->userFuncData.definition);
lua_gettable(L, LUA_REGISTRYINDEX);
if (!lua_isfunction(L, -1))
{
lua_getglobal(global, name.value);
lua_xmove(global, L, 1);
ctx->ice->ice("user-defined type function reference cannot be found in the registry");
return {std::nullopt, Reduction::Erroneous, {}, {}};
}
resetTypeFunctionState(L);
@ -816,31 +741,22 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
throw UserCancelError(ctx->ice->moduleName);
};
if (FFlag::LuauUserTypeFunPrintToError)
ctx->typeFunctionRuntime->messages.clear();
ctx->typeFunctionRuntime->messages.clear();
if (auto error = checkResultForError(L, name.value, lua_pcall(L, int(typeParams.size()), 1, 0)))
{
if (FFlag::LuauUserTypeFunPrintToError)
return {std::nullopt, Reduction::Erroneous, {}, {}, error, ctx->typeFunctionRuntime->messages};
else
return {std::nullopt, Reduction::Erroneous, {}, {}, error};
}
return {std::nullopt, Reduction::Erroneous, {}, {}, error, ctx->typeFunctionRuntime->messages};
// If the return value is not a type userdata, return with error message
if (!isTypeUserData(L, 1))
{
if (FFlag::LuauUserTypeFunPrintToError)
return {
std::nullopt,
Reduction::Erroneous,
{},
{},
format("'%s' type function: returned a non-type value", name.value),
ctx->typeFunctionRuntime->messages
};
else
return {std::nullopt, Reduction::Erroneous, {}, {}, format("'%s' type function: returned a non-type value", name.value)};
return {
std::nullopt,
Reduction::Erroneous,
{},
{},
format("'%s' type function: returned a non-type value", name.value),
ctx->typeFunctionRuntime->messages
};
}
TypeFunctionTypeId retTypeFunctionTypeId = getTypeUserData(L, 1);
@ -852,17 +768,9 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
// At least 1 error occurred while deserializing
if (runtimeBuilder->errors.size() > 0)
{
if (FFlag::LuauUserTypeFunPrintToError)
return {std::nullopt, Reduction::Erroneous, {}, {}, runtimeBuilder->errors.front(), ctx->typeFunctionRuntime->messages};
else
return {std::nullopt, Reduction::Erroneous, {}, {}, runtimeBuilder->errors.front()};
}
return {std::nullopt, Reduction::Erroneous, {}, {}, runtimeBuilder->errors.front(), ctx->typeFunctionRuntime->messages};
if (FFlag::LuauUserTypeFunPrintToError)
return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages};
else
return {retTypeId, Reduction::MaybeOk, {}, {}};
return {retTypeId, Reduction::MaybeOk, {}, {}, std::nullopt, ctx->typeFunctionRuntime->messages};
}
TypeFunctionReductionResult<TypeId> notTypeFunction(
@ -1098,21 +1006,18 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
lua_State* global = state.get();
if (FFlag::LuauUserTypeFunExportedAndLocal)
// Fetch to check if function is already registered
lua_pushlightuserdata(global, function);
lua_gettable(global, LUA_REGISTRYINDEX);
if (!lua_isnil(global, -1))
{
// Fetch to check if function is already registered
lua_pushlightuserdata(global, function);
lua_gettable(global, LUA_REGISTRYINDEX);
if (!lua_isnil(global, -1))
{
lua_pop(global, 1);
return std::nullopt;
}
lua_pop(global, 1);
return std::nullopt;
}
lua_pop(global, 1);
AstName name = function->name;
// Construct ParseResult containing the type function
@ -1166,19 +1071,10 @@ std::optional<std::string> TypeFunctionRuntime::registerFunction(AstStatTypeFunc
return format("Could not find '%s' type function in the global scope", name.value);
}
if (FFlag::LuauUserTypeFunExportedAndLocal)
{
// Store resulting function in the registry
lua_pushlightuserdata(global, function);
lua_xmove(L, global, 1);
lua_settable(global, LUA_REGISTRYINDEX);
}
else
{
// Store resulting function in the global environment
lua_xmove(L, global, 1);
lua_setglobal(global, name.value);
}
// Store resulting function in the registry
lua_pushlightuserdata(global, function);
lua_xmove(L, global, 1);
lua_settable(global, LUA_REGISTRYINDEX);
return std::nullopt;
}

View file

@ -15,9 +15,7 @@
LUAU_DYNAMIC_FASTINT(LuauTypeFunctionSerdeIterationLimit)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunPrintToError)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunThreadBuffer)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail)
@ -137,11 +135,9 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty)
return "number";
else if (auto s = get<TypeFunctionPrimitiveType>(ty); s && s->type == TypeFunctionPrimitiveType::Type::String)
return "string";
else if (auto s = get<TypeFunctionPrimitiveType>(ty);
FFlag::LuauUserTypeFunThreadBuffer && s && s->type == TypeFunctionPrimitiveType::Type::Thread)
else if (auto s = get<TypeFunctionPrimitiveType>(ty); s && s->type == TypeFunctionPrimitiveType::Type::Thread)
return "thread";
else if (auto s = get<TypeFunctionPrimitiveType>(ty);
FFlag::LuauUserTypeFunThreadBuffer && s && s->type == TypeFunctionPrimitiveType::Type::Buffer)
else if (auto s = get<TypeFunctionPrimitiveType>(ty); s && s->type == TypeFunctionPrimitiveType::Type::Buffer)
return "buffer";
else if (get<TypeFunctionUnknownType>(ty))
return "unknown";
@ -1759,8 +1755,8 @@ void registerTypesLibrary(lua_State* L)
{"boolean", createBoolean},
{"number", createNumber},
{"string", createString},
{FFlag::LuauUserTypeFunThreadBuffer ? "thread" : nullptr, FFlag::LuauUserTypeFunThreadBuffer ? createThread : nullptr},
{FFlag::LuauUserTypeFunThreadBuffer ? "buffer" : nullptr, FFlag::LuauUserTypeFunThreadBuffer ? createBuffer : nullptr},
{"thread", createThread},
{"buffer", createBuffer},
{nullptr, nullptr}
};
@ -1941,29 +1937,16 @@ void setTypeFunctionEnvironment(lua_State* L)
luaopen_base(L);
lua_pop(L, 1);
if (FFlag::LuauUserTypeFunPrintToError)
// Remove certain global functions from the base library
static const char* unavailableGlobals[] = {"gcinfo", "getfenv", "newproxy", "setfenv", "pcall", "xpcall"};
for (auto& name : unavailableGlobals)
{
// Remove certain global functions from the base library
static const char* unavailableGlobals[] = {"gcinfo", "getfenv", "newproxy", "setfenv", "pcall", "xpcall"};
for (auto& name : unavailableGlobals)
{
lua_pushcfunction(L, unsupportedFunction, name);
lua_setglobal(L, name);
}
lua_pushcfunction(L, unsupportedFunction, name);
lua_setglobal(L, name);
}
lua_pushcfunction(L, print, "print");
lua_setglobal(L, "print");
}
else
{
// Remove certain global functions from the base library
static const std::string unavailableGlobals[] = {"gcinfo", "getfenv", "newproxy", "setfenv", "pcall", "xpcall"};
for (auto& name : unavailableGlobals)
{
lua_pushcfunction(L, unsupportedFunction, "Removing global function from type function environment");
lua_setglobal(L, name.c_str());
}
}
lua_pushcfunction(L, print, "print");
lua_setglobal(L, "print");
}
void resetTypeFunctionState(lua_State* L)
@ -2495,12 +2478,10 @@ private:
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::String));
break;
case TypeFunctionPrimitiveType::Thread:
if (FFlag::LuauUserTypeFunThreadBuffer)
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Thread));
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Thread));
break;
case TypeFunctionPrimitiveType::Buffer:
if (FFlag::LuauUserTypeFunThreadBuffer)
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Buffer));
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Buffer));
break;
default:
break;

View file

@ -20,7 +20,6 @@
// currently, controls serialization, deserialization, and `type.copy`
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000);
LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer)
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
namespace Luau
@ -161,26 +160,10 @@ private:
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::String));
break;
case PrimitiveType::Thread:
if (FFlag::LuauUserTypeFunThreadBuffer)
{
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Thread));
}
else
{
std::string error = format("Argument of primitive type %s is not currently serializable by type functions", toString(ty).c_str());
state->errors.push_back(error);
}
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Thread));
break;
case PrimitiveType::Buffer:
if (FFlag::LuauUserTypeFunThreadBuffer)
{
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Buffer));
}
else
{
std::string error = format("Argument of primitive type %s is not currently serializable by type functions", toString(ty).c_str());
state->errors.push_back(error);
}
target = typeFunctionRuntime->typeArena.allocate(TypeFunctionPrimitiveType(TypeFunctionPrimitiveType::Buffer));
break;
case PrimitiveType::Function:
case PrimitiveType::Table:
@ -670,16 +653,10 @@ private:
target = state->ctx->builtins->stringType;
break;
case TypeFunctionPrimitiveType::Type::Thread:
if (FFlag::LuauUserTypeFunThreadBuffer)
target = state->ctx->builtins->threadType;
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized");
target = state->ctx->builtins->threadType;
break;
case TypeFunctionPrimitiveType::Type::Buffer:
if (FFlag::LuauUserTypeFunThreadBuffer)
target = state->ctx->builtins->bufferType;
else
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized");
target = state->ctx->builtins->bufferType;
break;
default:
state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized");

View file

@ -10,7 +10,7 @@
std::optional<std::string> getCurrentWorkingDirectory();
std::string normalizePath(std::string_view path);
std::string resolvePath(std::string_view relativePath, std::string_view baseFilePath);
std::optional<std::string> resolvePath(std::string_view relativePath, std::string_view baseFilePath);
std::optional<std::string> readFile(const std::string& name);
std::optional<std::string> readStdin();
@ -23,7 +23,7 @@ bool isDirectory(const std::string& path);
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback);
std::vector<std::string_view> splitPath(std::string_view path);
std::string joinPaths(const std::string& lhs, const std::string& rhs);
std::optional<std::string> getParentPath(const std::string& path);
std::string joinPaths(std::string_view lhs, std::string_view rhs);
std::optional<std::string> getParentPath(std::string_view path);
std::vector<std::string> getSourceFiles(int argc, char** argv);

View file

@ -20,6 +20,7 @@
#endif
#include <string.h>
#include <string_view>
#ifdef _WIN32
static std::wstring fromUtf8(const std::string& path)
@ -90,108 +91,76 @@ std::optional<std::string> getCurrentWorkingDirectory()
return std::nullopt;
}
// Returns the normal/canonical form of a path (e.g. "../subfolder/../module.luau" -> "../module.luau")
std::string normalizePath(std::string_view path)
{
return resolvePath(path, "");
}
const std::vector<std::string_view> components = splitPath(path);
std::vector<std::string_view> normalizedComponents;
// Takes a path that is relative to the file at baseFilePath and returns the path explicitly rebased onto baseFilePath.
// For absolute paths, baseFilePath will be ignored, and this function will resolve the path to a canonical path:
// (e.g. "/Users/.././Users/johndoe" -> "/Users/johndoe").
std::string resolvePath(std::string_view path, std::string_view baseFilePath)
{
std::vector<std::string_view> pathComponents;
std::vector<std::string_view> baseFilePathComponents;
const bool isAbsolute = isAbsolutePath(path);
// Dependent on whether the final resolved path is absolute or relative
// - if relative (when path and baseFilePath are both relative), resolvedPathPrefix remains empty
// - if absolute (if either path or baseFilePath are absolute), resolvedPathPrefix is "C:\", "/", etc.
std::string resolvedPathPrefix;
bool isResolvedPathRelative = false;
if (isAbsolutePath(path))
{
// path is absolute, we use path's prefix and ignore baseFilePath
size_t afterPrefix = path.find_first_of("\\/") + 1;
resolvedPathPrefix = path.substr(0, afterPrefix);
pathComponents = splitPath(path.substr(afterPrefix));
}
else
{
size_t afterPrefix = baseFilePath.find_first_of("\\/") + 1;
baseFilePathComponents = splitPath(baseFilePath.substr(afterPrefix));
if (isAbsolutePath(baseFilePath))
{
// path is relative and baseFilePath is absolute, we use baseFilePath's prefix
resolvedPathPrefix = baseFilePath.substr(0, afterPrefix);
}
else
{
// path and baseFilePath are both relative, we do not set a prefix (resolved path will be relative)
isResolvedPathRelative = true;
}
pathComponents = splitPath(path);
}
// Remove filename from components
if (!baseFilePathComponents.empty())
baseFilePathComponents.pop_back();
// Resolve the path by applying pathComponents to baseFilePathComponents
int numPrependedParents = 0;
for (std::string_view component : pathComponents)
// 1. Normalize path components
const size_t startIndex = isAbsolute ? 1 : 0;
for (size_t i = startIndex; i < components.size(); i++)
{
std::string_view component = components[i];
if (component == "..")
{
if (baseFilePathComponents.empty())
if (normalizedComponents.empty())
{
if (isResolvedPathRelative)
numPrependedParents++; // "../" will later be added to the beginning of the resolved path
if (!isAbsolute)
{
normalizedComponents.emplace_back("..");
}
}
else if (baseFilePathComponents.back() != "..")
else if (normalizedComponents.back() == "..")
{
baseFilePathComponents.pop_back(); // Resolve cases like "folder/subfolder/../../file" to "file"
normalizedComponents.emplace_back("..");
}
else
{
normalizedComponents.pop_back();
}
}
else if (component != "." && !component.empty())
else if (!component.empty() && component != ".")
{
baseFilePathComponents.push_back(component);
normalizedComponents.emplace_back(component);
}
}
// Create resolved path prefix for relative paths
if (isResolvedPathRelative)
std::string normalizedPath;
// 2. Add correct prefix to formatted path
if (isAbsolute)
{
if (numPrependedParents > 0)
{
resolvedPathPrefix.reserve(numPrependedParents * 3);
for (int i = 0; i < numPrependedParents; i++)
{
resolvedPathPrefix += "../";
}
}
else
{
resolvedPathPrefix = "./";
}
normalizedPath += components[0];
normalizedPath += "/";
}
else if (normalizedComponents.empty() || normalizedComponents[0] != "..")
{
normalizedPath += "./";
}
// Join baseFilePathComponents to form the resolved path
std::string resolvedPath = resolvedPathPrefix;
for (auto iter = baseFilePathComponents.begin(); iter != baseFilePathComponents.end(); ++iter)
// 3. Join path components to form the normalized path
for (auto iter = normalizedComponents.begin(); iter != normalizedComponents.end(); ++iter)
{
if (iter != baseFilePathComponents.begin())
resolvedPath += "/";
if (iter != normalizedComponents.begin())
normalizedPath += "/";
resolvedPath += *iter;
normalizedPath += *iter;
}
if (resolvedPath.size() > resolvedPathPrefix.size() && resolvedPath.back() == '/')
{
// Remove trailing '/' if present
resolvedPath.pop_back();
}
return resolvedPath;
if (normalizedPath.size() >= 2 && normalizedPath[normalizedPath.size() - 1] == '.' && normalizedPath[normalizedPath.size() - 2] == '.')
normalizedPath += "/";
return normalizedPath;
}
std::optional<std::string> resolvePath(std::string_view path, std::string_view baseFilePath)
{
std::optional<std::string> baseFilePathParent = getParentPath(baseFilePath);
if (!baseFilePathParent)
return std::nullopt;
return normalizePath(joinPaths(*baseFilePathParent, path));
}
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions)
@ -416,16 +385,16 @@ std::vector<std::string_view> splitPath(std::string_view path)
return components;
}
std::string joinPaths(const std::string& lhs, const std::string& rhs)
std::string joinPaths(std::string_view lhs, std::string_view rhs)
{
std::string result = lhs;
std::string result = std::string(lhs);
if (!result.empty() && result.back() != '/' && result.back() != '\\')
result += '/';
result += rhs;
return result;
}
std::optional<std::string> getParentPath(const std::string& path)
std::optional<std::string> getParentPath(std::string_view path)
{
if (path == "" || path == "." || path == "/")
return std::nullopt;
@ -441,7 +410,7 @@ std::optional<std::string> getParentPath(const std::string& path)
return "/";
if (slash != std::string::npos)
return path.substr(0, slash);
return std::string(path.substr(0, slash));
return "";
}
@ -471,10 +440,12 @@ std::vector<std::string> getSourceFiles(int argc, char** argv)
if (argv[i][0] == '-' && argv[i][1] != '\0')
continue;
if (isDirectory(argv[i]))
std::string normalized = normalizePath(argv[i]);
if (isDirectory(normalized))
{
traverseDirectory(
argv[i],
normalized,
[&](const std::string& name)
{
std::string ext = getExtension(name);
@ -486,7 +457,7 @@ std::vector<std::string> getSourceFiles(int argc, char** argv)
}
else
{
files.push_back(argv[i]);
files.push_back(normalized);
}
}

View file

@ -141,8 +141,17 @@ bool RequireResolver::resolveAndStoreDefaultPaths()
return false;
// resolvePath automatically sanitizes/normalizes the paths
resolvedRequire.identifier = resolvePath(pathToResolve, identifierContext);
resolvedRequire.absolutePath = resolvePath(pathToResolve, *absolutePathContext);
std::optional<std::string> identifier = resolvePath(pathToResolve, identifierContext);
std::optional<std::string> absolutePath = resolvePath(pathToResolve, *absolutePathContext);
if (!identifier || !absolutePath)
{
errorHandler.reportError("could not resolve require path");
return false;
}
resolvedRequire.identifier = std::move(*identifier);
resolvedRequire.absolutePath = std::move(*absolutePath);
}
else
{
@ -181,7 +190,7 @@ std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
else
{
// Require statement is being executed in a file, must resolve relative to CWD
requiringFile = resolvePath(requireContext.getPath(), joinPaths(*cwd, "stdin"));
requiringFile = normalizePath(joinPaths(*cwd, requireContext.getPath()));
}
}
std::replace(requiringFile.begin(), requiringFile.end(), '\\', '/');
@ -190,7 +199,7 @@ std::optional<std::string> RequireResolver::getRequiringContextAbsolute()
std::string RequireResolver::getRequiringContextRelative()
{
return requireContext.isStdin() ? "" : requireContext.getPath();
return requireContext.isStdin() ? "./" : requireContext.getPath();
}
bool RequireResolver::substituteAliasIfPresent(std::string& path)

View file

@ -13,7 +13,6 @@
#include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauCodeGenVectorDeadStoreElim)
LUAU_FASTFLAG(LuauCodeGenLerp)
namespace Luau
@ -501,7 +500,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.fcvt(temp4, temp3);
build.str(temp4, AddressA64(addr.base, addr.data + 8));
if (FFlag::LuauCodeGenVectorDeadStoreElim && inst.e.kind != IrOpKind::None)
if (inst.e.kind != IrOpKind::None)
{
RegisterA64 temp = regs.allocTemp(KindA64::w);
build.mov(temp, tagOp(inst.e));

View file

@ -17,7 +17,6 @@
#include "lgc.h"
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauCodeGenVectorDeadStoreElim)
LUAU_FASTFLAG(LuauCodeGenLerp)
namespace Luau
@ -301,7 +300,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 1), inst.c);
storeDoubleAsFloat(luauRegValueVector(vmRegOp(inst.a), 2), inst.d);
if (FFlag::LuauCodeGenVectorDeadStoreElim && inst.e.kind != IrOpKind::None)
if (inst.e.kind != IrOpKind::None)
build.mov(luauRegTag(vmRegOp(inst.a)), tagOp(inst.e));
break;
case IrCmd::STORE_TVALUE:

View file

@ -22,7 +22,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64)
LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAGVARIABLE(LuauCodeGenArithOpt)
LUAU_FASTFLAGVARIABLE(LuauCodeGenLimitLiveSlotReuse)
namespace Luau
@ -1314,17 +1313,12 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
break;
case IrCmd::ADD_NUM:
case IrCmd::SUB_NUM:
if (FFlag::LuauCodeGenArithOpt)
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
// a + 0.0 and a - (-0.0) can't be folded since the behavior is different for negative zero
// however, a - 0.0 and a + (-0.0) can be folded into a
if (*k == 0.0 && bool(signbit(*k)) == (inst.cmd == IrCmd::ADD_NUM))
substitute(function, inst, inst.a);
else
state.substituteOrRecord(inst, index);
}
// a + 0.0 and a - (-0.0) can't be folded since the behavior is different for negative zero
// however, a - 0.0 and a + (-0.0) can be folded into a
if (*k == 0.0 && bool(signbit(*k)) == (inst.cmd == IrCmd::ADD_NUM))
substitute(function, inst, inst.a);
else
state.substituteOrRecord(inst, index);
}
@ -1332,19 +1326,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.substituteOrRecord(inst, index);
break;
case IrCmd::MUL_NUM:
if (FFlag::LuauCodeGenArithOpt)
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (*k == 1.0) // a * 1.0 = a
substitute(function, inst, inst.a);
else if (*k == 2.0) // a * 2.0 = a + a
replace(function, block, index, {IrCmd::ADD_NUM, inst.a, inst.a});
else if (*k == -1.0) // a * -1.0 = -a
replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else
state.substituteOrRecord(inst, index);
}
if (*k == 1.0) // a * 1.0 = a
substitute(function, inst, inst.a);
else if (*k == 2.0) // a * 2.0 = a + a
replace(function, block, index, {IrCmd::ADD_NUM, inst.a, inst.a});
else if (*k == -1.0) // a * -1.0 = -a
replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else
state.substituteOrRecord(inst, index);
}
@ -1352,19 +1341,14 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
state.substituteOrRecord(inst, index);
break;
case IrCmd::DIV_NUM:
if (FFlag::LuauCodeGenArithOpt)
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (std::optional<double> k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b)))
{
if (*k == 1.0) // a / 1.0 = a
substitute(function, inst, inst.a);
else if (*k == -1.0) // a / -1.0 = -a
replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else if (int exp = 0; frexp(*k, &exp) == 0.5 && exp >= -1000 && exp <= 1000) // a / 2^k = a * 2^-k
replace(function, block, index, {IrCmd::MUL_NUM, inst.a, build.constDouble(1.0 / *k)});
else
state.substituteOrRecord(inst, index);
}
if (*k == 1.0) // a / 1.0 = a
substitute(function, inst, inst.a);
else if (*k == -1.0) // a / -1.0 = -a
replace(function, block, index, {IrCmd::UNM_NUM, inst.a});
else if (int exp = 0; frexp(*k, &exp) == 0.5 && exp >= -1000 && exp <= 1000) // a / 2^k = a * 2^-k
replace(function, block, index, {IrCmd::MUL_NUM, inst.a, build.constDouble(1.0 / *k)});
else
state.substituteOrRecord(inst, index);
}

View file

@ -9,8 +9,6 @@
#include "lobject.h"
LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorDeadStoreElim)
// TODO: optimization can be improved by knowing which registers are live in at each VM exit
namespace Luau
@ -326,27 +324,19 @@ static bool tryReplaceTagWithFullStore(
// And value store has to follow, as the pre-DSO code would not allow GC to observe an incomplete stack variable
if (tag != LUA_TNIL && regInfo.valueInstIdx != ~0u)
{
if (FFlag::LuauCodeGenVectorDeadStoreElim)
{
IrInst& prevValueInst = function.instructions[regInfo.valueInstIdx];
IrInst& prevValueInst = function.instructions[regInfo.valueInstIdx];
if (prevValueInst.cmd == IrCmd::STORE_VECTOR)
{
CODEGEN_ASSERT(prevValueInst.e.kind == IrOpKind::None);
IrOp prevValueX = prevValueInst.b;
IrOp prevValueY = prevValueInst.c;
IrOp prevValueZ = prevValueInst.d;
replace(function, block, instIndex, IrInst{IrCmd::STORE_VECTOR, targetOp, prevValueX, prevValueY, prevValueZ, tagOp});
}
else
{
IrOp prevValueOp = prevValueInst.b;
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, targetOp, tagOp, prevValueOp});
}
if (prevValueInst.cmd == IrCmd::STORE_VECTOR)
{
CODEGEN_ASSERT(prevValueInst.e.kind == IrOpKind::None);
IrOp prevValueX = prevValueInst.b;
IrOp prevValueY = prevValueInst.c;
IrOp prevValueZ = prevValueInst.d;
replace(function, block, instIndex, IrInst{IrCmd::STORE_VECTOR, targetOp, prevValueX, prevValueY, prevValueZ, tagOp});
}
else
{
IrOp prevValueOp = function.instructions[regInfo.valueInstIdx].b;
IrOp prevValueOp = prevValueInst.b;
replace(function, block, instIndex, IrInst{IrCmd::STORE_SPLIT_TVALUE, targetOp, tagOp, prevValueOp});
}
}
@ -385,7 +375,7 @@ static bool tryReplaceTagWithFullStore(
state.hasGcoToClear |= regInfo.maybeGco;
return true;
}
else if (FFlag::LuauCodeGenVectorDeadStoreElim && prev.cmd == IrCmd::STORE_VECTOR)
else if (prev.cmd == IrCmd::STORE_VECTOR)
{
// If the 'nil' is stored, we keep 'STORE_TAG Rn, tnil' as it writes the 'full' TValue
if (tag != LUA_TNIL)
@ -455,7 +445,7 @@ static bool tryReplaceValueWithFullStore(
regInfo.tvalueInstIdx = instIndex;
return true;
}
else if (FFlag::LuauCodeGenVectorDeadStoreElim && prev.cmd == IrCmd::STORE_VECTOR)
else if (prev.cmd == IrCmd::STORE_VECTOR)
{
IrOp prevTagOp = prev.e;
CODEGEN_ASSERT(prevTagOp.kind != IrOpKind::None);
@ -483,8 +473,6 @@ static bool tryReplaceVectorValueWithFullStore(
StoreRegInfo& regInfo
)
{
CODEGEN_ASSERT(FFlag::LuauCodeGenVectorDeadStoreElim);
// If the tag+value pair is established, we can mark both as dead and use a single split TValue store
if (regInfo.tagInstIdx != ~0u && regInfo.valueInstIdx != ~0u)
{
@ -631,29 +619,22 @@ static void markDeadStoresInInst(RemoveDeadStoreState& state, IrBuilder& build,
case IrCmd::STORE_VECTOR:
if (inst.a.kind == IrOpKind::VmReg)
{
if (FFlag::LuauCodeGenVectorDeadStoreElim)
{
int reg = vmRegOp(inst.a);
int reg = vmRegOp(inst.a);
if (function.cfg.captured.regs.test(reg))
return;
if (function.cfg.captured.regs.test(reg))
return;
StoreRegInfo& regInfo = state.info[reg];
StoreRegInfo& regInfo = state.info[reg];
if (tryReplaceVectorValueWithFullStore(state, build, function, block, index, regInfo))
break;
if (tryReplaceVectorValueWithFullStore(state, build, function, block, index, regInfo))
break;
// Partial value store can be removed by a new one if the tag is known
if (regInfo.knownTag != kUnknownTag)
state.killValueStore(regInfo);
// Partial value store can be removed by a new one if the tag is known
if (regInfo.knownTag != kUnknownTag)
state.killValueStore(regInfo);
regInfo.valueInstIdx = index;
regInfo.maybeGco = false;
}
else
{
state.useReg(vmRegOp(inst.a));
}
regInfo.valueInstIdx = index;
regInfo.maybeGco = false;
}
break;
case IrCmd::STORE_TVALUE:

View file

@ -14,7 +14,7 @@ inline bool isFlagExperimental(const char* flag)
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
"LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative
"StudioReportLuauAny2", // takes telemetry data for usage of any types
"LuauTableCloneClonesType", // requires fixes in lua-apps code, terrifyingly
"LuauTableCloneClonesType2", // requires fixes in lua-apps code, terrifyingly
"LuauSolverV2",
// makes sure we always have at least one entry
nullptr,

View file

@ -7,7 +7,6 @@
#include <array>
LUAU_FASTFLAGVARIABLE(LuauCompileDisabledBuiltins)
LUAU_FASTFLAGVARIABLE(LuauCompileMathLerp)
namespace Luau
@ -297,36 +296,33 @@ struct BuiltinVisitor : AstVisitor
, options(options)
, names(names)
{
if (FFlag::LuauCompileDisabledBuiltins)
builtinIsDisabled.fill(false);
if (const char* const* ptr = options.disabledBuiltins)
{
builtinIsDisabled.fill(false);
if (const char* const* ptr = options.disabledBuiltins)
for (; *ptr; ++ptr)
{
for (; *ptr; ++ptr)
if (const char* dot = strchr(*ptr, '.'))
{
if (const char* dot = strchr(*ptr, '.'))
AstName library = names.getWithType(*ptr, dot - *ptr).first;
AstName name = names.get(dot + 1);
if (library.value && name.value && getGlobalState(globals, name) == Global::Default)
{
AstName library = names.getWithType(*ptr, dot - *ptr).first;
AstName name = names.get(dot + 1);
Builtin builtin = Builtin{library, name};
if (library.value && name.value && getGlobalState(globals, name) == Global::Default)
{
Builtin builtin = Builtin{library, name};
if (int bfid = getBuiltinFunctionId(builtin, options); bfid >= 0)
builtinIsDisabled[bfid] = true;
}
if (int bfid = getBuiltinFunctionId(builtin, options); bfid >= 0)
builtinIsDisabled[bfid] = true;
}
else
}
else
{
if (AstName name = names.get(*ptr); name.value && getGlobalState(globals, name) == Global::Default)
{
if (AstName name = names.get(*ptr); name.value && getGlobalState(globals, name) == Global::Default)
{
Builtin builtin = Builtin{AstName(), name};
Builtin builtin = Builtin{AstName(), name};
if (int bfid = getBuiltinFunctionId(builtin, options); bfid >= 0)
builtinIsDisabled[bfid] = true;
}
if (int bfid = getBuiltinFunctionId(builtin, options); bfid >= 0)
builtinIsDisabled[bfid] = true;
}
}
}
@ -341,7 +337,7 @@ struct BuiltinVisitor : AstVisitor
int bfid = getBuiltinFunctionId(builtin, options);
if (FFlag::LuauCompileDisabledBuiltins && bfid >= 0 && builtinIsDisabled[bfid])
if (bfid >= 0 && builtinIsDisabled[bfid])
bfid = -1;
// getBuiltinFunctionId optimistically assumes all select() calls are builtin but actually the second argument must be a vararg

View file

@ -26,9 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
LUAU_FASTFLAGVARIABLE(LuauCompileOptimizeRevArith)
LUAU_FASTFLAGVARIABLE(LuauCompileLibraryConstants)
namespace Luau
{
@ -1624,8 +1621,7 @@ struct Compiler
return;
}
}
else if (FFlag::LuauCompileOptimizeRevArith && options.optimizationLevel >= 2 &&
(expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul))
else if (options.optimizationLevel >= 2 && (expr->op == AstExprBinary::Add || expr->op == AstExprBinary::Mul))
{
// Optimization: replace k*r with r*k when r is known to be a number (otherwise metamethods may be called)
if (LuauBytecodeType* ty = exprTypes.find(expr); ty && *ty == LBC_TYPE_NUMBER)
@ -4226,17 +4222,14 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
{
compiler.builtinsFoldLibraryK = true;
}
else if (FFlag::LuauCompileLibraryConstants)
else if (const char* const* ptr = options.librariesWithKnownMembers)
{
if (const char* const* ptr = options.librariesWithKnownMembers)
for (; *ptr; ++ptr)
{
for (; *ptr; ++ptr)
if (AstName name = names.get(*ptr); name.value && getGlobalState(compiler.globals, name) == Global::Default)
{
if (AstName name = names.get(*ptr); name.value && getGlobalState(compiler.globals, name) == Global::Default)
{
compiler.builtinsFoldLibraryK = true;
break;
}
compiler.builtinsFoldLibraryK = true;
break;
}
}
}

View file

@ -6,9 +6,6 @@
#include <vector>
#include <math.h>
LUAU_FASTFLAG(LuauCompileLibraryConstants)
LUAU_FASTFLAGVARIABLE(LuauVectorFolding)
namespace Luau
{
namespace Compile
@ -60,7 +57,7 @@ static void foldUnary(Constant& result, AstExprUnary::Op op, const Constant& arg
result.type = Constant::Type_Number;
result.valueNumber = -arg.valueNumber;
}
else if (FFlag::LuauVectorFolding && arg.type == Constant::Type_Vector)
else if (arg.type == Constant::Type_Vector)
{
result.type = Constant::Type_Vector;
result.valueVector[0] = -arg.valueVector[0];
@ -93,7 +90,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.type = Constant::Type_Number;
result.valueNumber = la.valueNumber + ra.valueNumber;
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
{
result.type = Constant::Type_Vector;
result.valueVector[0] = la.valueVector[0] + ra.valueVector[0];
@ -109,7 +106,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.type = Constant::Type_Number;
result.valueNumber = la.valueNumber - ra.valueNumber;
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
{
result.type = Constant::Type_Vector;
result.valueVector[0] = la.valueVector[0] - ra.valueVector[0];
@ -125,7 +122,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.type = Constant::Type_Number;
result.valueNumber = la.valueNumber * ra.valueNumber;
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
{
bool hadW = la.valueVector[3] != 0.0f || ra.valueVector[3] != 0.0f;
float resultW = la.valueVector[3] * ra.valueVector[3];
@ -139,7 +136,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
{
bool hadW = ra.valueVector[3] != 0.0f;
float resultW = float(la.valueNumber) * ra.valueVector[3];
@ -153,7 +150,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
{
bool hadW = la.valueVector[3] != 0.0f;
float resultW = la.valueVector[3] * float(ra.valueNumber);
@ -175,7 +172,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.type = Constant::Type_Number;
result.valueNumber = la.valueNumber / ra.valueNumber;
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
{
bool hadW = la.valueVector[3] != 0.0f || ra.valueVector[3] != 0.0f;
float resultW = la.valueVector[3] / ra.valueVector[3];
@ -189,7 +186,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
{
bool hadW = ra.valueVector[3] != 0.0f;
float resultW = float(la.valueNumber) / ra.valueVector[3];
@ -203,7 +200,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
{
bool hadW = la.valueVector[3] != 0.0f;
float resultW = la.valueVector[3] / float(ra.valueNumber);
@ -225,7 +222,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.type = Constant::Type_Number;
result.valueNumber = floor(la.valueNumber / ra.valueNumber);
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Vector)
{
bool hadW = la.valueVector[3] != 0.0f || ra.valueVector[3] != 0.0f;
float resultW = floor(la.valueVector[3] / ra.valueVector[3]);
@ -239,7 +236,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
else if (la.type == Constant::Type_Number && ra.type == Constant::Type_Vector)
{
bool hadW = ra.valueVector[3] != 0.0f;
float resultW = floor(float(la.valueNumber) / ra.valueVector[3]);
@ -253,7 +250,7 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l
result.valueVector[3] = resultW;
}
}
else if (FFlag::LuauVectorFolding && la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
else if (la.type == Constant::Type_Vector && ra.type == Constant::Type_Number)
{
bool hadW = la.valueVector[3] != 0.0f;
float resultW = floor(la.valueVector[3] / float(ra.valueNumber));
@ -474,24 +471,14 @@ struct ConstantVisitor : AstVisitor
if (foldLibraryK)
{
if (FFlag::LuauCompileLibraryConstants)
if (AstExprGlobal* eg = expr->expr->as<AstExprGlobal>())
{
if (AstExprGlobal* eg = expr->expr->as<AstExprGlobal>())
{
if (eg->name == "math")
result = foldBuiltinMath(expr->index);
// if we have a custom handler and the constant hasn't been resolved
if (libraryMemberConstantCb && result.type == Constant::Type_Unknown)
libraryMemberConstantCb(eg->name.value, expr->index.value, reinterpret_cast<Luau::CompileConstant*>(&result));
}
}
else
{
if (AstExprGlobal* eg = expr->expr->as<AstExprGlobal>(); eg && eg->name == "math")
{
if (eg->name == "math")
result = foldBuiltinMath(expr->index);
}
// if we have a custom handler and the constant hasn't been resolved
if (libraryMemberConstantCb && result.type == Constant::Type_Unknown)
libraryMemberConstantCb(eg->name.value, expr->index.value, reinterpret_cast<Luau::CompileConstant*>(&result));
}
}
}

View file

@ -3,8 +3,6 @@
#include "Luau/BytecodeBuilder.h"
LUAU_FASTFLAG(LuauCompileLibraryConstants)
namespace Luau
{
@ -182,8 +180,6 @@ static bool isMatchingGlobalMember(
const char* member
)
{
LUAU_ASSERT(FFlag::LuauCompileLibraryConstants);
if (AstExprGlobal* object = expr->expr->as<AstExprGlobal>())
return getGlobalState(globals, object->name) == Compile::Global::Default && object->name == library && expr->index == member;
@ -481,50 +477,45 @@ struct TypeMapVisitor : AstVisitor
if (node->index == "X" || node->index == "Y" || node->index == "Z")
{
recordResolvedType(node, &builtinTypes.numberType);
if (FFlag::LuauCompileLibraryConstants)
return false;
return false;
}
}
}
if (FFlag::LuauCompileLibraryConstants)
if (isMatchingGlobalMember(globals, node, "vector", "zero") || isMatchingGlobalMember(globals, node, "vector", "one"))
{
if (isMatchingGlobalMember(globals, node, "vector", "zero") || isMatchingGlobalMember(globals, node, "vector", "one"))
{
recordResolvedType(node, &builtinTypes.vectorType);
return false;
}
recordResolvedType(node, &builtinTypes.vectorType);
return false;
}
if (libraryMemberTypeCb)
if (libraryMemberTypeCb)
{
if (AstExprGlobal* object = node->expr->as<AstExprGlobal>())
{
if (AstExprGlobal* object = node->expr->as<AstExprGlobal>())
if (LuauBytecodeType ty = LuauBytecodeType(libraryMemberTypeCb(object->name.value, node->index.value)); ty != LBC_TYPE_ANY)
{
if (LuauBytecodeType ty = LuauBytecodeType(libraryMemberTypeCb(object->name.value, node->index.value)); ty != LBC_TYPE_ANY)
// TODO: 'resolvedExprs' is more limited than 'exprTypes' which limits full inference of more complex types that a user
// callback can return
switch (ty)
{
// TODO: 'resolvedExprs' is more limited than 'exprTypes' which limits full inference of more complex types that a user
// callback can return
switch (ty)
{
case LBC_TYPE_BOOLEAN:
resolvedExprs[node] = &builtinTypes.booleanType;
break;
case LBC_TYPE_NUMBER:
resolvedExprs[node] = &builtinTypes.numberType;
break;
case LBC_TYPE_STRING:
resolvedExprs[node] = &builtinTypes.stringType;
break;
case LBC_TYPE_VECTOR:
resolvedExprs[node] = &builtinTypes.vectorType;
break;
default:
break;
}
exprTypes[node] = ty;
return false;
case LBC_TYPE_BOOLEAN:
resolvedExprs[node] = &builtinTypes.booleanType;
break;
case LBC_TYPE_NUMBER:
resolvedExprs[node] = &builtinTypes.numberType;
break;
case LBC_TYPE_STRING:
resolvedExprs[node] = &builtinTypes.stringType;
break;
case LBC_TYPE_VECTOR:
resolvedExprs[node] = &builtinTypes.vectorType;
break;
default:
break;
}
exprTypes[node] = ty;
return false;
}
}
}

View file

@ -336,6 +336,7 @@ LUA_API const char* lua_getlightuserdataname(lua_State* L, int tag);
LUA_API void lua_clonefunction(lua_State* L, int idx);
LUA_API void lua_cleartable(lua_State* L, int idx);
LUA_API void lua_clonetable(lua_State* L, int idx);
LUA_API lua_Alloc lua_getallocf(lua_State* L, void** ud);

View file

@ -1539,6 +1539,16 @@ void lua_cleartable(lua_State* L, int idx)
luaH_clear(tt);
}
void lua_clonetable(lua_State* L, int idx)
{
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
LuaTable* tt = luaH_clone(L, hvalue(t));
sethvalue(L, L->top, tt);
api_incr_top(L);
}
lua_Callbacks* lua_callbacks(lua_State* L)
{
return &L->global->cb;

View file

@ -269,7 +269,13 @@ static int buffer_readbits(lua_State* L)
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
uint64_t data = 0;
#if LUAU_BIG_ENDIAN
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
data = (data << 8) + uint8_t(((char*)buf)[i]);
#else
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
#endif
uint64_t subbyteoffset = bitoffset & 0x7;
uint64_t mask = (1ull << bitcount) - 1;
@ -299,14 +305,28 @@ static int buffer_writebits(lua_State* L)
unsigned endbyte = unsigned((bitoffset + bitcount + 7) / 8);
uint64_t data = 0;
#if LUAU_BIG_ENDIAN
for (int i = int(endbyte) - 1; i >= int(startbyte); i--)
data = data * 256 + uint8_t(((char*)buf)[i]);
#else
memcpy(&data, (char*)buf + startbyte, endbyte - startbyte);
#endif
uint64_t subbyteoffset = bitoffset & 0x7;
uint64_t mask = ((1ull << bitcount) - 1) << subbyteoffset;
data = (data & ~mask) | ((uint64_t(value) << subbyteoffset) & mask);
#if LUAU_BIG_ENDIAN
for (int i = int(startbyte); i < int(endbyte); i++)
{
((char*)buf)[i] = data & 0xff;
data >>= 8;
}
#else
memcpy((char*)buf + startbyte, &data, endbyte - startbyte);
#endif
return 0;
}

View file

@ -8,8 +8,6 @@
#include <stdio.h>
#include <stdlib.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauDebugInfoInvArgLeftovers, false)
static lua_State* getthread(lua_State* L, int* arg)
{
if (lua_isthread(L, 1))
@ -110,7 +108,7 @@ static int db_info(lua_State* L)
default:
// restore stack state of another thread as 'f' option might not have been visited yet
if (DFFlag::LuauDebugInfoInvArgLeftovers && L != L1)
if (L != L1)
lua_settop(L1, l1top);
luaL_argerror(L, arg + 2, "invalid option");

View file

@ -6,7 +6,6 @@
#include <math.h>
LUAU_FASTFLAGVARIABLE(LuauVectorMetatable)
LUAU_FASTFLAGVARIABLE(LuauVector2Constructor)
static int vector_create(lua_State* L)
@ -261,8 +260,6 @@ static int vector_max(lua_State* L)
static int vector_index(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauVectorMetatable);
const float* v = luaL_checkvector(L, 1);
size_t namelen = 0;
const char* name = luaL_checklstring(L, 2, &namelen);
@ -307,8 +304,6 @@ static const luaL_Reg vectorlib[] = {
static void createmetatable(lua_State* L)
{
LUAU_ASSERT(FFlag::LuauVectorMetatable);
lua_createtable(L, 0, 1); // create metatable for vectors
// push dummy vector
@ -345,8 +340,7 @@ int luaopen_vector(lua_State* L)
lua_setfield(L, -2, "one");
#endif
if (FFlag::LuauVectorMetatable)
createmetatable(L);
createmetatable(L);
return 1;
}

View file

@ -23,11 +23,7 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost)
LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
LUAU_FASTINT(LuauRecursionLimit)
LUAU_FASTFLAG(LuauCompileOptimizeRevArith)
LUAU_FASTFLAG(LuauCompileLibraryConstants)
LUAU_FASTFLAG(LuauVectorFolding)
LUAU_FASTFLAG(LuauVector2Constants)
LUAU_FASTFLAG(LuauCompileDisabledBuiltins)
using namespace Luau;
@ -1491,8 +1487,6 @@ RETURN R0 1
TEST_CASE("ConstantFoldVectorArith")
{
ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true};
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3), vector.create(2, 4, 8); return a + b", 0, 2), R"(
LOADK R0 K0 [3, 6, 11]
RETURN R0 1
@ -1558,8 +1552,6 @@ RETURN R0 1
TEST_CASE("ConstantFoldVectorArith4Wide")
{
ScopedFastFlag luauVectorFolding{FFlag::LuauVectorFolding, true};
CHECK_EQ("\n" + compileFunction("local n = 2; local a, b = vector.create(1, 2, 3, 4), vector.create(2, 4, 8, 1); return a + b", 0, 2), R"(
LOADK R0 K0 [3, 6, 11, 5]
RETURN R0 1
@ -5147,8 +5139,6 @@ RETURN R0 1
TEST_CASE("VectorConstantFields")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ("\n" + compileFunction("return vector.one, vector.zero", 0, 2), R"(
LOADK R0 K0 [1, 1, 1]
LOADK R1 K1 [0, 0, 0]
@ -5169,8 +5159,6 @@ RETURN R0 1
TEST_CASE("CustomConstantFields")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ("\n" + compileFunction("return test.some_nil, test.some_boolean, test.some_number, test.some_string", 0, 2), R"(
LOADNIL R0
LOADB R1 1
@ -7904,8 +7892,6 @@ RETURN R0 1
TEST_CASE("BuiltinFoldingProhibitedInOptions")
{
ScopedFastFlag luauCompileDisabledBuiltins{FFlag::LuauCompileDisabledBuiltins, true};
Luau::BytecodeBuilder bcb;
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
Luau::CompileOptions options;
@ -9096,8 +9082,6 @@ RETURN R0 1
TEST_CASE("ArithRevK")
{
ScopedFastFlag sff(FFlag::LuauCompileOptimizeRevArith, true);
// - and / have special optimized form for reverse constants; in absence of type information, we can't optimize other ops
CHECK_EQ(
"\n" + compileFunction0(R"(

View file

@ -35,10 +35,8 @@ LUAU_FASTFLAG(LuauMathLerp)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_DYNAMIC_FASTFLAG(LuauStackLimit)
LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoInvArgLeftovers)
LUAU_FASTFLAG(LuauVectorLibNativeCodegen)
LUAU_FASTFLAG(LuauVectorLibNativeDot)
LUAU_FASTFLAG(LuauVectorMetatable)
LUAU_FASTFLAG(LuauVector2Constructor)
LUAU_FASTFLAG(LuauBufferBitMethods2)
LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse)
@ -897,7 +895,6 @@ TEST_CASE("VectorLibrary")
{
ScopedFastFlag luauVectorLibNativeCodegen{FFlag::LuauVectorLibNativeCodegen, true};
ScopedFastFlag luauVectorLibNativeDot{FFlag::LuauVectorLibNativeDot, true};
ScopedFastFlag luauVectorMetatable{FFlag::LuauVectorMetatable, true};
ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true};
lua_CompileOptions copts = defaultOptions();
@ -1024,8 +1021,6 @@ TEST_CASE("DateTime")
TEST_CASE("Debug")
{
ScopedFastFlag luauDebugInfoInvArgLeftovers{DFFlag::LuauDebugInfoInvArgLeftovers, true};
runConformance("debug.lua");
}
@ -1392,6 +1387,25 @@ TEST_CASE("ApiTables")
CHECK(strcmp(lua_tostring(L, -1), "test") == 0);
lua_pop(L, 1);
// lua_clonetable
lua_clonetable(L, -1);
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
CHECK(lua_tonumber(L, -1) == 123.0);
lua_pop(L, 1);
// modify clone
lua_pushnumber(L, 456.0);
lua_rawsetfield(L, -2, "key");
// remove clone
lua_pop(L, 1);
// check original
CHECK(lua_getfield(L, -1, "key") == LUA_TNUMBER);
CHECK(lua_tonumber(L, -1) == 123.0);
lua_pop(L, 1);
// lua_cleartable
lua_cleartable(L, -1);
lua_pushnil(L);

View file

@ -342,8 +342,11 @@ ParseResult Fixture::matchParseErrorPrefix(const std::string& source, const std:
return result;
}
ModulePtr Fixture::getMainModule()
ModulePtr Fixture::getMainModule(bool forAutocomplete)
{
if (forAutocomplete && !FFlag::LuauSolverV2)
return frontend.moduleResolverForAutocomplete.getModule(fromString(mainModuleName));
return frontend.moduleResolver.getModule(fromString(mainModuleName));
}
@ -366,9 +369,9 @@ std::optional<PrimitiveType::Type> Fixture::getPrimitiveType(TypeId ty)
return std::nullopt;
}
std::optional<TypeId> Fixture::getType(const std::string& name)
std::optional<TypeId> Fixture::getType(const std::string& name, bool forAutocomplete)
{
ModulePtr module = getMainModule();
ModulePtr module = getMainModule(forAutocomplete);
REQUIRE(module);
if (!module->hasModuleScope())
@ -520,6 +523,9 @@ void Fixture::registerTestTypes()
void Fixture::dumpErrors(const CheckResult& cr)
{
if (hasDumpedErrors)
return;
hasDumpedErrors = true;
std::string error = getErrors(cr);
if (!error.empty())
MESSAGE(error);
@ -527,6 +533,9 @@ void Fixture::dumpErrors(const CheckResult& cr)
void Fixture::dumpErrors(const ModulePtr& module)
{
if (hasDumpedErrors)
return;
hasDumpedErrors = true;
std::stringstream ss;
dumpErrors(ss, module->errors);
if (!ss.str().empty())
@ -535,6 +544,9 @@ void Fixture::dumpErrors(const ModulePtr& module)
void Fixture::dumpErrors(const Module& module)
{
if (hasDumpedErrors)
return;
hasDumpedErrors = true;
std::stringstream ss;
dumpErrors(ss, module.errors);
if (!ss.str().empty())

View file

@ -27,7 +27,6 @@
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauVectorDefinitionsExtra)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
@ -89,11 +88,11 @@ struct Fixture
// Verify a parse error occurs and the parse error message has the specified prefix
ParseResult matchParseErrorPrefix(const std::string& source, const std::string& prefix);
ModulePtr getMainModule();
ModulePtr getMainModule(bool forAutocomplete = false);
SourceModule* getMainSourceModule();
std::optional<PrimitiveType::Type> getPrimitiveType(TypeId ty);
std::optional<TypeId> getType(const std::string& name);
std::optional<TypeId> getType(const std::string& name, bool forAutocomplete = false);
TypeId requireType(const std::string& name);
TypeId requireType(const ModuleName& moduleName, const std::string& name);
TypeId requireType(const ModulePtr& module, const std::string& name);
@ -113,8 +112,6 @@ struct Fixture
// In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
ScopedFastFlag sff_LuauVectorDefinitionsExtra{FFlag::LuauVectorDefinitionsExtra, true};
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
// This is useful for tracking down violations of Luau's memory model.
ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true};
@ -142,11 +139,14 @@ struct Fixture
void registerTestTypes();
LoadDefinitionFileResult loadDefinition(const std::string& source, bool forAutocomplete = false);
private:
bool hasDumpedErrors = false;
};
struct BuiltinsFixture : Fixture
{
BuiltinsFixture(bool prepareAutocomplete = false);
explicit BuiltinsFixture(bool prepareAutocomplete = false);
};
std::optional<std::string> pathExprToModuleName(const ModuleName& currentModuleName, const std::vector<std::string_view>& segments);
@ -180,6 +180,18 @@ std::optional<TypeId> linearSearchForBinding(Scope* scope, const char* name);
void registerHiddenTypes(Frontend* frontend);
void createSomeClasses(Frontend* frontend);
template <typename E>
const E* findError(const CheckResult& result)
{
for (const auto& e : result.errors)
{
if (auto p = get<E>(e))
return p;
}
return nullptr;
}
template<typename BaseFixture>
struct DifferFixtureGeneric : BaseFixture
{
@ -329,3 +341,51 @@ using DifferFixtureWithBuiltins = DifferFixtureGeneric<BuiltinsFixture>;
} \
} \
} while (false)
#define LUAU_REQUIRE_ERROR(result, Type) \
do \
{ \
using T = Type; \
const auto& res = (result); \
if (!findError<T>(res)) \
{ \
dumpErrors(res); \
REQUIRE_MESSAGE(false, "Expected to find " #Type " error"); \
} \
} while (false)
#define LUAU_CHECK_ERROR(result, Type) \
do \
{ \
using T = Type; \
const auto& res = (result); \
if (!findError<T>(res)) \
{ \
dumpErrors(res); \
CHECK_MESSAGE(false, "Expected to find " #Type " error"); \
} \
} while (false)
#define LUAU_REQUIRE_NO_ERROR(result, Type) \
do \
{ \
using T = Type; \
const auto& res = (result); \
if (findError<T>(res)) \
{ \
dumpErrors(res); \
REQUIRE_MESSAGE(false, "Expected to find no " #Type " error"); \
} \
} while (false)
#define LUAU_CHECK_NO_ERROR(result, Type) \
do \
{ \
using T = Type; \
const auto& res = (result); \
if (findError<T>(res)) \
{ \
dumpErrors(res); \
CHECK_MESSAGE(false, "Expected to find no " #Type " error"); \
} \
} while (false)

View file

@ -714,6 +714,77 @@ TEST_SUITE_END();
TEST_SUITE_BEGIN("FragmentAutocompleteTests");
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete")
{
ToStringOptions opt;
opt.exhaustive = true;
opt.exhaustive = true;
opt.functionTypeArguments = true;
opt.maxTableLength = 0;
opt.maxTypeLength = 0;
auto checkAndExamine = [&](const std::string& src, const std::string& idName, const std::string& idString)
{
check(src, getOptions());
auto id = getType(idName, true);
LUAU_ASSERT(id);
CHECK_EQ(Luau::toString(*id, opt), idString);
};
auto getTypeFromModule = [](ModulePtr module, const std::string& name) -> std::optional<TypeId>
{
if (!module->hasModuleScope())
return std::nullopt;
return lookupName(module->getModuleScope(), name);
};
auto fragmentACAndCheck = [&](const std::string& updated,
const Position& pos,
const std::string& idName,
const std::string& srcIdString,
const std::string& fragIdString)
{
FragmentAutocompleteResult result = autocompleteFragment(updated, pos, std::nullopt);
auto fragId = getTypeFromModule(result.incrementalModule, idName);
LUAU_ASSERT(fragId);
CHECK_EQ(Luau::toString(*fragId, opt), fragIdString);
auto srcId = getType(idName, true);
LUAU_ASSERT(srcId);
CHECK_EQ(Luau::toString(*srcId, opt), srcIdString);
};
const std::string source = R"(local module = {}
f
return module)";
const std::string updated1 = R"(local module = {}
function module.a
return module)";
const std::string updated2 = R"(local module = {}
function module.ab
return module)";
{
ScopedFastFlag sff{FFlag::LuauSolverV2, false};
checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
// early return since the following checking will fail, which it shouldn't!
// fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
// fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
}
{
ScopedFastFlag sff{FFlag::LuauSolverV2, true};
checkAndExamine(source, "module", "{ }");
// [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment
// early return since the following checking will fail, which it shouldn't!
return;
fragmentACAndCheck(updated1, Position{1, 17}, "module", "{ }", "{ a: (%error-id%: unknown) -> () }");
fragmentACAndCheck(updated2, Position{1, 18}, "module", "{ }", "{ ab: (%error-id%: unknown) -> () }");
}
}
TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "can_autocomplete_simple_property_access")
{

View file

@ -16,6 +16,7 @@ LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(DebugLuauFreezeArena);
LUAU_FASTFLAG(DebugLuauMagicTypes);
LUAU_FASTFLAG(LuauReferenceAllocatorInNewSolver);
LUAU_FASTFLAG(LuauSelectivelyRetainDFGArena)
namespace
{
@ -1541,4 +1542,34 @@ TEST_CASE_FIXTURE(FrontendFixture, "check_module_references_allocator")
CHECK_EQ(module->names.get(), source->names.get());
}
TEST_CASE_FIXTURE(FrontendFixture, "dfg_data_cleared_on_retain_type_graphs_unset")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauSelectivelyRetainDFGArena, true}
};
fileResolver.source["game/A"] = R"(
local a = 1
local b = 2
local c = 3
return {x = a, y = b, z = c}
)";
frontend.options.retainFullTypeGraphs = true;
frontend.check("game/A");
auto mod = frontend.moduleResolver.getModule("game/A");
CHECK(!mod->defArena.allocator.empty());
CHECK(!mod->keyArena.allocator.empty());
// We should check that the dfg arena is empty once retainFullTypeGraphs is unset
frontend.options.retainFullTypeGraphs = false;
frontend.markDirty("game/A");
frontend.check("game/A");
mod = frontend.moduleResolver.getModule("game/A");
CHECK(mod->defArena.allocator.empty());
CHECK(mod->keyArena.allocator.empty());
}
TEST_SUITE_END();

View file

@ -13,8 +13,6 @@
#include <limits.h>
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodeGenVectorDeadStoreElim)
LUAU_FASTFLAG(LuauCodeGenArithOpt)
using namespace Luau::CodeGen;
@ -1725,8 +1723,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications")
{
ScopedFastFlag luauCodeGenArithOpt{FFlag::LuauCodeGenArithOpt, true};
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
@ -4472,8 +4468,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNumber")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4497,8 +4491,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverVector")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4522,8 +4514,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverVector")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4547,8 +4537,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverNil")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4571,8 +4559,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverNil")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4595,8 +4581,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "NumberOverCombinedVector")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4622,8 +4606,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedVector")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
@ -4649,8 +4631,6 @@ bb_0:
TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedNumber")
{
ScopedFastFlag luauCodeGenVectorDeadStoreElim{FFlag::LuauCodeGenVectorDeadStoreElim, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);

View file

@ -16,8 +16,6 @@
#include <memory>
#include <string_view>
LUAU_FASTFLAG(LuauCompileLibraryConstants)
static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant)
{
// While 'vector' library constants are a Luau built-in, their constant value depends on the embedder LUA_VECTOR_SIZE value
@ -2115,8 +2113,6 @@ bb_bytecode_1:
TEST_CASE("LibraryFieldTypesAndConstants")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
@ -2153,8 +2149,6 @@ bb_bytecode_1:
TEST_CASE("LibraryFieldTypesAndConstants")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssembly(
R"(
@ -2189,8 +2183,6 @@ bb_bytecode_1:
TEST_CASE("LibraryFieldTypesAndConstantsCApi")
{
ScopedFastFlag luauCompileLibraryConstants{FFlag::LuauCompileLibraryConstants, true};
CHECK_EQ(
"\n" + getCodegenAssemblyUsingCApi(
R"(

View file

@ -12,10 +12,14 @@
#include "doctest.h"
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#if __APPLE__
#include <TargetConditionals.h>
@ -215,21 +219,43 @@ TEST_CASE("PathResolution")
std::string prefix = "/";
#endif
CHECK(resolvePath(prefix + "Users/modules/module.luau", "") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "./a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/a/string/that/should/be/ignored") == prefix + "Users/modules/module.luau");
CHECK(resolvePath(prefix + "Users/modules/module.luau", "/Users/modules") == prefix + "Users/modules/module.luau");
// tuple format: {inputPath, inputBaseFilePath, expected}
std::vector<std::tuple<std::string, std::string, std::string>> tests = {
// 1. Basic path resolution
// a. Relative to a relative path that begins with './'
{"./dep", "./src/modules/module.luau", "./src/modules/dep"},
{"../dep", "./src/modules/module.luau", "./src/dep"},
{"../../dep", "./src/modules/module.luau", "./dep"},
{"../../", "./src/modules/module.luau", "./"},
CHECK(resolvePath("../module", "") == "../module");
CHECK(resolvePath("../../module", "") == "../../module");
CHECK(resolvePath("../module/..", "") == "../");
CHECK(resolvePath("../module/../..", "") == "../../");
// b. Relative to a relative path that begins with '../'
{"./dep", "../src/modules/module.luau", "../src/modules/dep"},
{"../dep", "../src/modules/module.luau", "../src/dep"},
{"../../dep", "../src/modules/module.luau", "../dep"},
{"../../", "../src/modules/module.luau", "../"},
CHECK(resolvePath("../dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../dependency/", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../../../../../Users/dependency", prefix + "Users/modules/module.luau") == prefix + "Users/dependency");
CHECK(resolvePath("../..", prefix + "Users/modules/module.luau") == prefix);
// c. Relative to an absolute path
{"./dep", prefix + "src/modules/module.luau", prefix + "src/modules/dep"},
{"../dep", prefix + "src/modules/module.luau", prefix + "src/dep"},
{"../../dep", prefix + "src/modules/module.luau", prefix + "dep"},
{"../../", prefix + "src/modules/module.luau", prefix},
// 2. Check behavior for extraneous ".."
// a. Relative paths retain '..' and append if needed
{"../../../", "./src/modules/module.luau", "../"},
{"../../../", "../src/modules/module.luau", "../../"},
// b. Absolute paths ignore '..' if already at root
{"../../../", prefix + "src/modules/module.luau", prefix},
};
for (const auto& [inputPath, inputBaseFilePath, expected] : tests)
{
std::optional<std::string> resolved = resolvePath(inputPath, inputBaseFilePath);
CHECK(resolved);
CHECK_EQ(resolved, expected);
}
}
TEST_CASE("PathNormalization")
@ -240,34 +266,57 @@ TEST_CASE("PathNormalization")
std::string prefix = "/";
#endif
// Relative path
std::optional<std::string> result = normalizePath("../../modules/module");
CHECK(result);
std::string normalized = *result;
std::vector<std::string> variants = {
"./.././.././modules/./module/", "placeholder/../../../modules/module", "../placeholder/placeholder2/../../../modules/module"
};
for (const std::string& variant : variants)
{
result = normalizePath(variant);
CHECK(result);
CHECK(normalized == *result);
}
// pair format: {input, expected}
std::vector<std::pair<std::string, std::string>> tests = {
// 1. Basic formatting checks
{"", "./"},
{".", "./"},
{"..", "../"},
{"a/relative/path", "./a/relative/path"},
// Absolute path
result = normalizePath(prefix + "Users/modules/module");
CHECK(result);
normalized = *result;
variants = {
"Users/Users/Users/.././.././modules/./module/",
"placeholder/../Users/..//Users/modules/module",
"Users/../placeholder/placeholder2/../../Users/modules/module"
// 2. Paths containing extraneous '.' and '/' symbols
{"./remove/extraneous/symbols/", "./remove/extraneous/symbols"},
{"./remove/extraneous//symbols", "./remove/extraneous/symbols"},
{"./remove/extraneous/symbols/.", "./remove/extraneous/symbols"},
{"./remove/extraneous/./symbols", "./remove/extraneous/symbols"},
{"../remove/extraneous/symbols/", "../remove/extraneous/symbols"},
{"../remove/extraneous//symbols", "../remove/extraneous/symbols"},
{"../remove/extraneous/symbols/.", "../remove/extraneous/symbols"},
{"../remove/extraneous/./symbols", "../remove/extraneous/symbols"},
{prefix + "remove/extraneous/symbols/", prefix + "remove/extraneous/symbols"},
{prefix + "remove/extraneous//symbols", prefix + "remove/extraneous/symbols"},
{prefix + "remove/extraneous/symbols/.", prefix + "remove/extraneous/symbols"},
{prefix + "remove/extraneous/./symbols", prefix + "remove/extraneous/symbols"},
// 3. Paths containing '..'
// a. '..' removes the erasable component before it
{"./remove/me/..", "./remove"},
{"./remove/me/../", "./remove"},
{"../remove/me/..", "../remove"},
{"../remove/me/../", "../remove"},
{prefix + "remove/me/..", prefix + "remove"},
{prefix + "remove/me/../", prefix + "remove"},
// b. '..' stays if path is relative and component is non-erasable
{"./..", "../"},
{"./../", "../"},
{"../..", "../../"},
{"../../", "../../"},
// c. '..' disappears if path is absolute and component is non-erasable
{prefix + "..", prefix},
};
for (const std::string& variant : variants)
for (const auto& [input, expected] : tests)
{
result = normalizePath(prefix + variant);
CHECK(result);
CHECK(normalized == *result);
CHECK_EQ(normalizePath(input), expected);
}
}
@ -489,6 +538,25 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasHasIllegalFormat")
assertOutputContainsAll({"false", " is not a valid alias"});
}
TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary")
{
char executable[] = "luau";
std::vector<std::string> paths = {
getLuauDirectory(PathType::Relative) + "/tests/require/without_config/dependency.luau",
getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/dependency.luau"
};
for (const std::string& path : paths)
{
std::vector<char> pathStr(path.size() + 1);
strncpy(pathStr.data(), path.c_str(), path.size());
pathStr[path.size()] = '\0';
char* argv[2] = {executable, pathStr.data()};
CHECK_EQ(replMain(2, argv), 0);
}
}
TEST_CASE("ParseAliases")
{
std::string configJson = R"({

View file

@ -10,12 +10,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauUserTypeFunFixNoReadWrite)
LUAU_FASTFLAG(LuauUserTypeFunFixInner)
LUAU_FASTFLAG(LuauUserTypeFunPrintToError)
LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal)
LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer)
LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs)
LUAU_FASTFLAG(LuauUserTypeFunGenerics)
LUAU_FASTFLAG(LuauUserTypeFunCloneTail)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -226,7 +223,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_number_methods_work")
TEST_CASE_FIXTURE(BuiltinsFixture, "thread_and_buffer_types")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunThreadBuffer{FFlag::LuauUserTypeFunThreadBuffer, true};
LUAU_REQUIRE_NO_ERRORS(check(R"(
type function work_with_thread(x)
@ -931,7 +927,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_2")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunUpdateAllEnvs{FFlag::LuauUserTypeFunUpdateAllEnvs, true};
CheckResult result = check(R"(
type function first(arg)
@ -955,8 +950,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_2")
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_calling_each_other_3")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true};
ScopedFastFlag luauUserTypeFunUpdateAllEnvs{FFlag::LuauUserTypeFunUpdateAllEnvs, true};
CheckResult result = check(R"(
-- this function should not see 'fourth' function when invoked from 'third' that sees it
@ -1281,7 +1274,6 @@ local a: foo<> = "a"
TEST_CASE_FIXTURE(BuiltinsFixture, "implicit_export")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true};
fileResolver.source["game/A"] = R"(
type function concat(a, b)
@ -1309,7 +1301,6 @@ local b: Test.Concat<'third', 'fourth'>
TEST_CASE_FIXTURE(BuiltinsFixture, "local_scope")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true};
CheckResult result = check(R"(
type function foo()
@ -1332,7 +1323,6 @@ local a = test()
TEST_CASE_FIXTURE(BuiltinsFixture, "explicit_export")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunExportedAndLocal{FFlag::LuauUserTypeFunExportedAndLocal, true};
fileResolver.source["game/A"] = R"(
export type function concat(a, b)
@ -1359,7 +1349,6 @@ local b: Test.concat<'third', 'fourth'>
TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunPrintToError{FFlag::LuauUserTypeFunPrintToError, true};
CheckResult result = check(R"(
type function t0(a)
@ -1378,7 +1367,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_error")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunPrintToError{FFlag::LuauUserTypeFunPrintToError, true};
CheckResult result = check(R"(
type function t0(a)
@ -1399,7 +1387,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_error")
TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result")
{
ScopedFastFlag solverV2{FFlag::LuauSolverV2, true};
ScopedFastFlag luauUserTypeFunPrintToError{FFlag::LuauUserTypeFunPrintToError, true};
CheckResult result = check(R"(
type function t0(a)
@ -1891,4 +1878,22 @@ local function ok(idx: pass<test>): (number, ...string) -> (string, ...number) r
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_eqsat_opaque")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauUserTypeFunGenerics, true}, {FFlag::DebugLuauEqSatSimplification, true}};
CheckResult _ = check(R"(
type function t0(a)
error("test")
end
local v: t0<string & number>
)");
TypeArena arena;
auto ty = requireType("v");
auto simplifier = EqSatSimplification::newSimplifier(NotNull{&arena}, frontend.builtinTypes);
auto simplified = eqSatSimplify(NotNull{simplifier.get()}, ty);
REQUIRE(simplified);
CHECK_EQ("t0<number & string>", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access)
}
TEST_SUITE_END();

View file

@ -12,7 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTypestateBuiltins2)
LUAU_FASTFLAG(LuauStringFormatArityFix)
LUAU_FASTFLAG(LuauTableCloneClonesType)
LUAU_FASTFLAG(LuauTableCloneClonesType2)
LUAU_FASTFLAG(LuauStringFormatErrorSuppression)
TEST_SUITE_BEGIN("BuiltinTests");
@ -1178,8 +1178,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_does_not_retroactively_block_mu
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauTypestateBuiltins2)
if (FFlag::LuauSolverV2 && FFlag::LuauTypestateBuiltins2)
{
CHECK_EQ("{ a: number, q: string } | { read a: number, read q: string }", toString(requireType("t1"), {/*exhaustive */ true}));
// before the assignment, it's `t1`
@ -1600,7 +1599,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_clone_type_states")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauTableCloneClonesType)
if (FFlag::LuauTableCloneClonesType2)
{
CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, z: number }");
CHECK_EQ(toString(requireType("t2"), {true}), "{ x: number, y: number }");

View file

@ -22,6 +22,8 @@ LUAU_FASTFLAG(LuauRetrySubtypingWithoutHiddenPack)
LUAU_FASTFLAG(LuauTableKeysAreRValues)
LUAU_FASTFLAG(LuauAllowNilAssignmentToIndexer)
LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope)
LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope)
LUAU_FASTFLAG(LuauDontInPlaceMutateTableType)
TEST_SUITE_BEGIN("TableTests");
@ -3816,7 +3818,10 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati
TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible")
{
ScopedFastFlag _{FFlag::LuauTrackInteriorFreeTypesOnScope, true};
ScopedFastFlag sffs[] = {
{FFlag::LuauTrackInteriorFreeTypesOnScope, true},
{FFlag::LuauTrackInteriorFreeTablesOnScope, true},
};
CheckResult result = check(R"(
local function f(s): string
@ -3832,7 +3837,7 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
CHECK(toString(result.errors[0]) == "Parameter 's' has been reduced to never. This function is not callable with any possible value.");
CHECK(
toString(result.errors[1]) ==
"Parameter 's' is required to be a subtype of '{- read absolutely_no_scalar_has_this_method: (never) -> (unknown, ...unknown) -}' here."
"Parameter 's' is required to be a subtype of '{ read absolutely_no_scalar_has_this_method: (never) -> (unknown, ...unknown) }' here."
);
CHECK(toString(result.errors[2]) == "Parameter 's' is required to be a subtype of 'string' here.");
CHECK_EQ("(never) -> string", toString(requireType("f")));
@ -5067,4 +5072,33 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "read_only_property_reads")
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "multiple_fields_in_literal")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauDontInPlaceMutateTableType, true},
};
auto result = check(R"(
type Foo = {
[string]: {
Min: number,
Max: number
}
}
local Foos: Foo = {
["Foo"] = {
Min = -1,
Max = 1
},
["Foo"] = {
Min = -1,
Max = 1
}
}
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -23,7 +23,6 @@ LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTINT(LuauNormalizeCacheLimit);
LUAU_FASTINT(LuauRecursionLimit);
LUAU_FASTINT(LuauTypeInferRecursionLimit);
LUAU_FASTFLAG(LuauNewSolverVisitErrorExprLvalues)
LUAU_FASTFLAG(InferGlobalTypes)
using namespace Luau;
@ -1197,13 +1196,23 @@ TEST_CASE_FIXTURE(Fixture, "type_infer_recursion_limit_normalizer")
validateErrors(result.errors);
REQUIRE_MESSAGE(!result.errors.empty(), getErrors(result));
CHECK(1 == result.errors.size());
if (FFlag::LuauSolverV2)
CHECK(Location{{3, 22}, {3, 42}} == result.errors[0].location);
{
CHECK(3 == result.errors.size());
CHECK(Location{{2, 22}, {2, 41}} == result.errors[0].location);
CHECK(Location{{3, 22}, {3, 42}} == result.errors[1].location);
CHECK(Location{{3, 23}, {3, 40}} == result.errors[2].location);
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[1]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[2]));
}
else
{
CHECK(1 == result.errors.size());
CHECK(Location{{3, 12}, {3, 46}} == result.errors[0].location);
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
CHECK_EQ("Code is too complex to typecheck! Consider simplifying the code around this area", toString(result.errors[0]));
}
}
TEST_CASE_FIXTURE(Fixture, "type_infer_cache_limit_normalizer")
@ -1708,7 +1717,7 @@ TEST_CASE_FIXTURE(Fixture, "react_lua_follow_free_type_ub")
TEST_CASE_FIXTURE(Fixture, "visit_error_nodes_in_lvalue")
{
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewSolverVisitErrorExprLvalues, true}};
ScopedFastFlag _{FFlag::LuauSolverV2, true};
// This should always fail to parse, but shouldn't assert. Previously this
// would assert as we end up _roughly_ parsing this (with a lot of error