diff --git a/Analysis/include/Luau/EqSatSimplificationImpl.h b/Analysis/include/Luau/EqSatSimplificationImpl.h index c9f49d52..e021baa8 100644 --- a/Analysis/include/Luau/EqSatSimplificationImpl.h +++ b/Analysis/include/Luau/EqSatSimplificationImpl.h @@ -149,7 +149,7 @@ using EType = EqSat::Language< struct StringCache { Allocator allocator; - DenseHashMap strings{{}}; + DenseHashMap strings{{}}; std::vector views; StringId add(std::string_view s); diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index 306c6413..871471a4 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -63,7 +63,7 @@ void check( NotNull builtinTypes, NotNull simplifier, NotNull typeFunctionRuntime, - NotNull sharedState, + NotNull unifierState, NotNull limits, DcrLogger* logger, const SourceModule& sourceModule, @@ -116,14 +116,14 @@ private: std::optional 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 lookupPackAnnotation(AstTypePack* annotation); - TypeId lookupExpectedType(AstExpr* expr); - TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena); + std::optional lookupPackAnnotation(AstTypePack* annotation) const; + TypeId lookupExpectedType(AstExpr* expr) const; + TypePackId lookupExpectedPack(AstExpr* expr, TypeArena& arena) const; TypePackId reconstructPack(AstArray 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 tryStripUnionFromNil(TypeId ty); + std::optional 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); diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 03e1bb2f..c3bed421 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -40,7 +40,7 @@ struct InConditionalContext TypeContext* typeContext; TypeContext oldValue; - InConditionalContext(TypeContext* c) + explicit InConditionalContext(TypeContext* c) : typeContext(c) , oldValue(*c) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 78503fed..01c97547 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -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(vectorTy); + TypeId vectorTy = it->second.type; + ClassType* vectorCls = getMutable(vectorTy); - vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed}); - TableType* metatableTy = Luau::getMutable(vectorCls->metatable); + vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed}); + TableType* metatableTy = Luau::getMutable(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 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 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(t: Table, 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()); - if (FFlag::LuauTableCloneClonesType) + if (FFlag::LuauTableCloneClonesType2) attachMagicFunction(ttv->props["clone"].type(), std::make_shared()); if (FFlag::LuauTypestateBuiltins2) attachMagicFunction(ttv->props["freeze"].type(), std::make_shared()); @@ -1405,7 +1401,7 @@ std::optional> MagicClone::handleOldSolver( WithPredicate withPredicate ) { - LUAU_ASSERT(FFlag::LuauTableCloneClonesType); + LUAU_ASSERT(FFlag::LuauTableCloneClonesType2); auto [paramPack, _predicates] = withPredicate; @@ -1429,7 +1425,7 @@ std::optional> MagicClone::handleOldSolver( bool MagicClone::infer(const MagicFunctionCallContext& context) { - LUAU_ASSERT(FFlag::LuauTableCloneClonesType); + LUAU_ASSERT(FFlag::LuauTableCloneClonesType2); TypeArena* arena = context.solver->arena; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 57fdccab..90feb1a6 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -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()) { - if (auto function = stat->as()) + // 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(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(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(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(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(tf.type); ty && ty->userFuncData.definition) - userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); - } - - level++; + if (auto ty = get(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(tf.type); ty && ty->userFuncData.definition) - userFuncData.environment_DEPRECATED[name] = ty->userFuncData.definition; - } + if (auto ty = get(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(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(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(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(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()) { - 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; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 1d0dc41a..5b0c73e7 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -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, NotNulladdType(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(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, NotNulladdType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); + + if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) + trackInteriorFreeType(constraint->scope, newUpperBound); + TableType* upperTable = getMutable(newUpperBound); LUAU_ASSERT(upperTable); @@ -2637,6 +2631,10 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( NotNull 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(newUpperBound); LUAU_ASSERT(tt); TypeId propType = freshType(arena, builtinTypes, scope); diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index dc16cb07..0042d6fb 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -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; } diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index 709dafb5..5927c773 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -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()(s); - if (uint32_t* it = strings.find(hash)) + /* Important subtlety: This use of DenseHashMap + * is okay because std::hash 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(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 parts; parts.reserve(tfun->typeArguments.size()); for (TypeId part : tfun->typeArguments) diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 14ff1f5e..1ce35d76 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -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(); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index bfa7c532..80398bf7 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -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(there.tops)) { diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index c50cd16e..5c7ea4d9 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -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 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()); + 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& 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. // diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 1e78acf7..fb312da9 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -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 -#include -#include LUAU_FASTFLAG(DebugLuauMagicTypes) @@ -176,7 +173,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor DenseHashSet mentionedFunctions{nullptr}; DenseHashSet mentionedFunctionPacks{nullptr}; - InternalTypeFunctionFinder(std::vector& declStack) + explicit InternalTypeFunctionFinder(std::vector& 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 TypeChecker2::lookupPackAnnotation(AstTypePack* annotation) +std::optional TypeChecker2::lookupPackAnnotation(AstTypePack* annotation) const { TypePackId* tp = module->astResolvedTypePacks.find(annotation); if (tp != nullptr) @@ -565,7 +562,7 @@ std::optional 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 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 TypeChecker2::tryStripUnionFromNil(TypeId ty) +std::optional TypeChecker2::tryStripUnionFromNil(TypeId ty) const { if (const UnionType* utv = get(ty)) { diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 8860b251..9af1599d 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -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 void handleTypeFunctionReduction(T subject, TypeFunctionReductionResult 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 userDefinedTypeFunction( { auto typeFunction = getMutable(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 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 error = ctx->typeFunctionRuntime->registerFunction(definition.first)) { - if (std::optional 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 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 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 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 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 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 notTypeFunction( @@ -1098,21 +1006,18 @@ std::optional 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 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; } diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 3766224b..de1302fc 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -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(ty); s && s->type == TypeFunctionPrimitiveType::Type::String) return "string"; - else if (auto s = get(ty); - FFlag::LuauUserTypeFunThreadBuffer && s && s->type == TypeFunctionPrimitiveType::Type::Thread) + else if (auto s = get(ty); s && s->type == TypeFunctionPrimitiveType::Type::Thread) return "thread"; - else if (auto s = get(ty); - FFlag::LuauUserTypeFunThreadBuffer && s && s->type == TypeFunctionPrimitiveType::Type::Buffer) + else if (auto s = get(ty); s && s->type == TypeFunctionPrimitiveType::Type::Buffer) return "buffer"; else if (get(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; diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index 4aa9344f..6b7fa419 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -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"); diff --git a/CLI/include/Luau/FileUtils.h b/CLI/include/Luau/FileUtils.h index f723c765..80e36378 100644 --- a/CLI/include/Luau/FileUtils.h +++ b/CLI/include/Luau/FileUtils.h @@ -10,7 +10,7 @@ std::optional getCurrentWorkingDirectory(); std::string normalizePath(std::string_view path); -std::string resolvePath(std::string_view relativePath, std::string_view baseFilePath); +std::optional resolvePath(std::string_view relativePath, std::string_view baseFilePath); std::optional readFile(const std::string& name); std::optional readStdin(); @@ -23,7 +23,7 @@ bool isDirectory(const std::string& path); bool traverseDirectory(const std::string& path, const std::function& callback); std::vector splitPath(std::string_view path); -std::string joinPaths(const std::string& lhs, const std::string& rhs); -std::optional getParentPath(const std::string& path); +std::string joinPaths(std::string_view lhs, std::string_view rhs); +std::optional getParentPath(std::string_view path); std::vector getSourceFiles(int argc, char** argv); diff --git a/CLI/src/FileUtils.cpp b/CLI/src/FileUtils.cpp index 2207f678..d54d94e0 100644 --- a/CLI/src/FileUtils.cpp +++ b/CLI/src/FileUtils.cpp @@ -20,6 +20,7 @@ #endif #include +#include #ifdef _WIN32 static std::wstring fromUtf8(const std::string& path) @@ -90,108 +91,76 @@ std::optional 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 components = splitPath(path); + std::vector 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 pathComponents; - std::vector 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 resolvePath(std::string_view path, std::string_view baseFilePath) +{ + std::optional baseFilePathParent = getParentPath(baseFilePath); + if (!baseFilePathParent) + return std::nullopt; + + return normalizePath(joinPaths(*baseFilePathParent, path)); } bool hasFileExtension(std::string_view name, const std::vector& extensions) @@ -416,16 +385,16 @@ std::vector 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 getParentPath(const std::string& path) +std::optional getParentPath(std::string_view path) { if (path == "" || path == "." || path == "/") return std::nullopt; @@ -441,7 +410,7 @@ std::optional 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 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 getSourceFiles(int argc, char** argv) } else { - files.push_back(argv[i]); + files.push_back(normalized); } } diff --git a/CLI/src/Require.cpp b/CLI/src/Require.cpp index 94bdd544..1039f85c 100644 --- a/CLI/src/Require.cpp +++ b/CLI/src/Require.cpp @@ -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 identifier = resolvePath(pathToResolve, identifierContext); + std::optional 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 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 RequireResolver::getRequiringContextAbsolute() std::string RequireResolver::getRequiringContextRelative() { - return requireContext.isStdin() ? "" : requireContext.getPath(); + return requireContext.isStdin() ? "./" : requireContext.getPath(); } bool RequireResolver::substituteAliasIfPresent(std::string& path) diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 2ec72445..1eece87f 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -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)); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index e0ece9da..0f99959f 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -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: diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index ce44f5d1..e2fefbed 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -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 k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b))) { - if (std::optional 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 k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b))) { - if (std::optional 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 k = function.asDoubleOp(inst.b.kind == IrOpKind::Constant ? inst.b : state.tryGetValue(inst.b))) { - if (std::optional 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); } diff --git a/CodeGen/src/OptimizeDeadStore.cpp b/CodeGen/src/OptimizeDeadStore.cpp index 8362cf2b..1483e4a2 100644 --- a/CodeGen/src/OptimizeDeadStore.cpp +++ b/CodeGen/src/OptimizeDeadStore.cpp @@ -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: diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index 62a0d77d..cb8dbcaf 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -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, diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index af0a5f02..64d4d3f2 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -7,7 +7,6 @@ #include -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 diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 8076d157..29cf5c05 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -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; } } } diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index 24e272d7..818a5bf7 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -6,9 +6,6 @@ #include #include -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()) { - if (AstExprGlobal* eg = expr->expr->as()) - { - 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(&result)); - } - } - else - { - if (AstExprGlobal* eg = expr->expr->as(); 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(&result)); } } } diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index fd9074a1..9fe9798e 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -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()) 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()) { - if (AstExprGlobal* object = node->expr->as()) + 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; } } } diff --git a/VM/include/lua.h b/VM/include/lua.h index a1525a65..8531ed33 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -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); diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 98afca7b..a956fa94 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -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; diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 643d3a9c..7edec3ad 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -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; } diff --git a/VM/src/ldblib.cpp b/VM/src/ldblib.cpp index cab4dd6f..ff9fdd76 100644 --- a/VM/src/ldblib.cpp +++ b/VM/src/ldblib.cpp @@ -8,8 +8,6 @@ #include #include -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"); diff --git a/VM/src/lveclib.cpp b/VM/src/lveclib.cpp index ff1fd269..c08087bd 100644 --- a/VM/src/lveclib.cpp +++ b/VM/src/lveclib.cpp @@ -6,7 +6,6 @@ #include -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; } diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 8256b24a..e88c77e7 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -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"( diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index fdf868df..60c9b313 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -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); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index e22e52b0..3a8d2cfc 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -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 Fixture::getPrimitiveType(TypeId ty) return std::nullopt; } -std::optional Fixture::getType(const std::string& name) +std::optional 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()) diff --git a/tests/Fixture.h b/tests/Fixture.h index 23ec7e2b..c202075b 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -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 getPrimitiveType(TypeId ty); - std::optional getType(const std::string& name); + std::optional 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 pathExprToModuleName(const ModuleName& currentModuleName, const std::vector& segments); @@ -180,6 +180,18 @@ std::optional linearSearchForBinding(Scope* scope, const char* name); void registerHiddenTypes(Frontend* frontend); void createSomeClasses(Frontend* frontend); +template +const E* findError(const CheckResult& result) +{ + for (const auto& e : result.errors) + { + if (auto p = get(e)) + return p; + } + + return nullptr; +} + template struct DifferFixtureGeneric : BaseFixture { @@ -329,3 +341,51 @@ using DifferFixtureWithBuiltins = DifferFixtureGeneric; } \ } \ } while (false) + +#define LUAU_REQUIRE_ERROR(result, Type) \ + do \ + { \ + using T = Type; \ + const auto& res = (result); \ + if (!findError(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(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(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(res)) \ + { \ + dumpErrors(res); \ + CHECK_MESSAGE(false, "Expected to find no " #Type " error"); \ + } \ + } while (false) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 3ddd6a30..326a5c98 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -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 + { + 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") { diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index b0ba86ed..fce96e48 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -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(); diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index ba4e7f04..629c3696 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -13,8 +13,6 @@ #include 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); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 362c1016..2a5c23fd 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -16,8 +16,6 @@ #include #include -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"( diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index ff00a681..59a1af3b 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -12,10 +12,14 @@ #include "doctest.h" #include +#include #include #include #include #include +#include +#include +#include #if __APPLE__ #include @@ -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> 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 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 result = normalizePath("../../modules/module"); - CHECK(result); - std::string normalized = *result; - std::vector 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> 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 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 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"({ diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 66c397a1..98c8d2a8 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -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): (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 + )"); + 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", toString(simplified->result)); // NOLINT(bugprone-unchecked-optional-access) +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 750f066c..1e67739d 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -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 }"); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 3ef618d1..735f75ed 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -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(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 4264c777..297a5153 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -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